/* * Copyright 1999,2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.webdav.methods; import java.io.IOException; import java.util.HashMap; import java.util.Hashtable; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.DocumentBuilder; import org.modeshape.common.i18n.TextI18n; import org.modeshape.webdav.ITransaction; import org.modeshape.webdav.IWebdavStore; import org.modeshape.webdav.StoredObject; import org.modeshape.webdav.WebdavStatus; import org.modeshape.webdav.exceptions.LockFailedException; import org.modeshape.webdav.exceptions.WebdavException; import org.modeshape.webdav.fromcatalina.XMLWriter; import org.modeshape.webdav.locking.IResourceLocks; import org.modeshape.webdav.locking.LockedObject; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; public class DoLock extends AbstractMethod { private final IWebdavStore store; private final IResourceLocks resourceLocks; private final boolean readOnly; private boolean macLockRequest = false; private boolean exclusive = false; private String type = null; private String lockOwner = null; private String path = null; private String parentPath = null; private String userAgent = null; public DoLock( IWebdavStore store, IResourceLocks resourceLocks, boolean readOnly ) { this.store = store; this.resourceLocks = resourceLocks; this.readOnly = readOnly; } @Override public void execute( ITransaction transaction, HttpServletRequest req, HttpServletResponse resp ) throws IOException, LockFailedException { logger.trace("-- " + this.getClass().getName()); if (readOnly) { resp.sendError(WebdavStatus.SC_FORBIDDEN); } else { path = getRelativePath(req); parentPath = getParentPath(getCleanPath(path)); if (!isUnlocked(transaction, req, resourceLocks, path)) { resp.setStatus(WebdavStatus.SC_LOCKED); return; // resource is locked } if (!isUnlocked(transaction, req, resourceLocks, parentPath)) { resp.setStatus(WebdavStatus.SC_LOCKED); return; // parent is locked } // Mac OS Finder (whether 10.4.x or 10.5) can't store files // because executing a LOCK without lock information causes a // SC_BAD_REQUEST userAgent = req.getHeader("User-Agent"); if (userAgent != null && userAgent.contains("Darwin")) { macLockRequest = true; String timeString = Long.toString(System.currentTimeMillis()); lockOwner = userAgent.concat(timeString); } String tempLockOwner = "doLock" + System.currentTimeMillis() + req.toString(); if (resourceLocks.lock(transaction, path, tempLockOwner, false, 0, TEMP_TIMEOUT, TEMPORARY)) { try { if (req.getHeader("If") != null) { doRefreshLock(transaction, req, resp); } else { doLock(transaction, req, resp); } } catch (LockFailedException e) { resp.sendError(WebdavStatus.SC_LOCKED); logger.error(e, new TextI18n("Lockfailed exception")); } finally { resourceLocks.unlockTemporaryLockedObjects(transaction, path, tempLockOwner); } } } } private void doLock( ITransaction transaction, HttpServletRequest req, HttpServletResponse resp ) throws IOException, LockFailedException { StoredObject so = store.getStoredObject(transaction, path); if (so != null) { doLocking(transaction, req, resp); } else { // resource doesn't exist, null-resource lock doNullResourceLock(transaction, req, resp); } so = null; exclusive = false; type = null; lockOwner = null; } private void doLocking( ITransaction transaction, HttpServletRequest req, HttpServletResponse resp ) throws IOException { // Tests if LockObject on requested path exists, and if so, tests // exclusivity LockedObject lo = resourceLocks.getLockedObjectByPath(transaction, path); if (lo != null) { if (lo.isExclusive()) { sendLockFailError(req, resp); return; } } try { // Thats the locking itself executeLock(transaction, req, resp); } catch (ServletException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); logger.trace(e.toString()); } catch (LockFailedException e) { sendLockFailError(req, resp); } finally { lo = null; } } private void doNullResourceLock( ITransaction transaction, HttpServletRequest req, HttpServletResponse resp ) throws IOException { StoredObject parentSo, nullSo = null; try { parentSo = store.getStoredObject(transaction, parentPath); if (parentPath != null && parentSo == null) { store.createFolder(transaction, parentPath); } else if (parentPath != null && parentSo != null && parentSo.isResource()) { resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED); return; } nullSo = store.getStoredObject(transaction, path); if (nullSo == null) { // resource doesn't exist store.createResource(transaction, path); // Transmit expects 204 response-code, not 201 if (userAgent != null && userAgent.contains("Transmit")) { logger.trace("DoLock.execute() : do workaround for user agent '" + userAgent + "'"); resp.setStatus(WebdavStatus.SC_NO_CONTENT); } else { resp.setStatus(WebdavStatus.SC_CREATED); } } else { // resource already exists, could not execute null-resource lock sendLockFailError(req, resp); return; } nullSo = store.getStoredObject(transaction, path); if (nullSo == null) { resp.setStatus(WebdavStatus.SC_NOT_FOUND); } else { // define the newly created resource as null-resource nullSo.setNullResource(true); // Thats the locking itself executeLock(transaction, req, resp); } } catch (LockFailedException e) { sendLockFailError(req, resp); } catch (WebdavException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); logger.error(e, new TextI18n("Webdav exception")); } catch (ServletException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); logger.error(e, new TextI18n("Servlet exception")); } finally { parentSo = null; nullSo = null; } } private void doRefreshLock( ITransaction transaction, HttpServletRequest req, HttpServletResponse resp ) throws IOException, LockFailedException { String[] lockTokens = getLockIdFromIfHeader(req); String lockToken = null; if (lockTokens != null) { lockToken = lockTokens[0]; } if (lockToken != null) { // Getting LockObject of specified lockToken in If header LockedObject refreshLo = resourceLocks.getLockedObjectByID(transaction, lockToken); if (refreshLo != null) { int timeout = getTimeout(req); refreshLo.refreshTimeout(timeout); // sending success response generateXMLReport(resp, refreshLo); refreshLo = null; } else { // no LockObject to given lockToken resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED); } } else { resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED); } } // ------------------------------------------------- helper methods private void executeLock( ITransaction transaction, HttpServletRequest req, HttpServletResponse resp ) throws LockFailedException, IOException, ServletException { // Mac OS lock request workaround if (macLockRequest) { logger.trace("DoLock.execute() : do workaround for user agent '" + userAgent + "'"); doMacLockRequestWorkaround(transaction, req, resp); } else { // Getting LockInformation from request if (getLockInformation(req, resp)) { int depth = getDepth(req); int lockDuration = getTimeout(req); boolean lockSuccess = false; if (exclusive) { lockSuccess = resourceLocks.exclusiveLock(transaction, path, lockOwner, depth, lockDuration); } else { lockSuccess = resourceLocks.sharedLock(transaction, path, lockOwner, depth, lockDuration); } if (lockSuccess) { // Locks successfully placed - return information about LockedObject lo = resourceLocks.getLockedObjectByPath(transaction, path); if (lo != null) { generateXMLReport(resp, lo); } else { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { sendLockFailError(req, resp); throw new LockFailedException(); } } else { // information for LOCK could not be read successfully resp.setContentType("text/xml; charset=UTF-8"); resp.sendError(WebdavStatus.SC_BAD_REQUEST); } } } private boolean getLockInformation( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException { Node lockInfoNode = null; DocumentBuilder documentBuilder = null; documentBuilder = getDocumentBuilder(); try { Document document = documentBuilder.parse(new InputSource(req.getInputStream())); // Get the root element of the document lockInfoNode = document.getDocumentElement(); if (lockInfoNode != null) { NodeList childList = lockInfoNode.getChildNodes(); Node lockScopeNode = null; Node lockTypeNode = null; Node lockOwnerNode = null; Node currentNode = null; String nodeName = null; for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE || currentNode.getNodeType() == Node.TEXT_NODE) { nodeName = currentNode.getNodeName(); if (nodeName.endsWith("locktype")) { lockTypeNode = currentNode; } if (nodeName.endsWith("lockscope")) { lockScopeNode = currentNode; } if (nodeName.endsWith("owner")) { lockOwnerNode = currentNode; } } else { return false; } } if (lockScopeNode != null) { String scope = null; childList = lockScopeNode.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE) { scope = currentNode.getNodeName(); if (scope.endsWith("exclusive")) { exclusive = true; } else if (scope.equals("shared")) { exclusive = false; } } } if (scope == null) { return false; } } else { return false; } if (lockTypeNode != null) { childList = lockTypeNode.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE) { type = currentNode.getNodeName(); if (type.endsWith("write")) { type = "write"; } else if (type.equals("read")) { type = "read"; } } } if (type == null) { return false; } } else { return false; } if (lockOwnerNode != null) { childList = lockOwnerNode.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); switch (currentNode.getNodeType()) { case Node.ELEMENT_NODE: lockOwner = currentNode.getFirstChild().getNodeValue(); break; case Node.TEXT_NODE: lockOwner = currentNode.getNodeValue(); break; } } } if (lockOwner == null) { return false; } } else { return false; } } catch (DOMException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); logger.error(e, new TextI18n("DOM exception")); return false; } catch (SAXException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); logger.error(e, new TextI18n("SAX exception")); return false; } return true; } private int getTimeout( HttpServletRequest req ) { int lockDuration = DEFAULT_TIMEOUT; String lockDurationStr = req.getHeader("Timeout"); if (lockDurationStr == null) { lockDuration = DEFAULT_TIMEOUT; } else { int commaPos = lockDurationStr.indexOf(','); // if multiple timeouts, just use the first one if (commaPos != -1) { lockDurationStr = lockDurationStr.substring(0, commaPos); } if (lockDurationStr.startsWith("Second-")) { lockDuration = Integer.valueOf(lockDurationStr.substring(7)); } else { if (lockDurationStr.equalsIgnoreCase("infinity")) { lockDuration = MAX_TIMEOUT; } else { try { lockDuration = Integer.valueOf(lockDurationStr); } catch (NumberFormatException e) { lockDuration = MAX_TIMEOUT; } } } if (lockDuration <= 0) { lockDuration = DEFAULT_TIMEOUT; } if (lockDuration > MAX_TIMEOUT) { lockDuration = MAX_TIMEOUT; } } return lockDuration; } private void generateXMLReport( HttpServletResponse resp, LockedObject lockedObject ) throws IOException { HashMap<String, String> namespaces = new HashMap<String, String>(); namespaces.put("DAV:", "D"); resp.setStatus(WebdavStatus.SC_OK); resp.setContentType("text/xml; charset=UTF-8"); XMLWriter generatedXML = new XMLWriter(resp.getWriter(), namespaces); generatedXML.writeXMLHeader(); generatedXML.writeElement("DAV::prop", XMLWriter.OPENING); generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.OPENING); generatedXML.writeElement("DAV::activelock", XMLWriter.OPENING); generatedXML.writeElement("DAV::locktype", XMLWriter.OPENING); generatedXML.writeProperty("DAV::" + type); generatedXML.writeElement("DAV::locktype", XMLWriter.CLOSING); generatedXML.writeElement("DAV::lockscope", XMLWriter.OPENING); if (exclusive) { generatedXML.writeProperty("DAV::exclusive"); } else { generatedXML.writeProperty("DAV::shared"); } generatedXML.writeElement("DAV::lockscope", XMLWriter.CLOSING); int depth = lockedObject.getLockDepth(); generatedXML.writeElement("DAV::depth", XMLWriter.OPENING); if (depth == INFINITY) { generatedXML.writeText("Infinity"); } else { generatedXML.writeText(String.valueOf(depth)); } generatedXML.writeElement("DAV::depth", XMLWriter.CLOSING); generatedXML.writeElement("DAV::owner", XMLWriter.OPENING); generatedXML.writeElement("DAV::href", XMLWriter.OPENING); generatedXML.writeText(lockOwner); generatedXML.writeElement("DAV::href", XMLWriter.CLOSING); generatedXML.writeElement("DAV::owner", XMLWriter.CLOSING); long timeout = lockedObject.getTimeoutMillis(); generatedXML.writeElement("DAV::timeout", XMLWriter.OPENING); generatedXML.writeText("Second-" + timeout / 1000); generatedXML.writeElement("DAV::timeout", XMLWriter.CLOSING); String lockToken = lockedObject.getID(); generatedXML.writeElement("DAV::locktoken", XMLWriter.OPENING); generatedXML.writeElement("DAV::href", XMLWriter.OPENING); generatedXML.writeText("opaquelocktoken:" + lockToken); generatedXML.writeElement("DAV::href", XMLWriter.CLOSING); generatedXML.writeElement("DAV::locktoken", XMLWriter.CLOSING); generatedXML.writeElement("DAV::activelock", XMLWriter.CLOSING); generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.CLOSING); generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING); resp.addHeader("Lock-Token", "<opaquelocktoken:" + lockToken + ">"); generatedXML.sendData(); } private void doMacLockRequestWorkaround( ITransaction transaction, HttpServletRequest req, HttpServletResponse resp ) throws LockFailedException, IOException { LockedObject lo; int depth = getDepth(req); int lockDuration = getTimeout(req); if (lockDuration < 0 || lockDuration > MAX_TIMEOUT) { lockDuration = DEFAULT_TIMEOUT; } boolean lockSuccess = false; lockSuccess = resourceLocks.exclusiveLock(transaction, path, lockOwner, depth, lockDuration); if (lockSuccess) { // Locks successfully placed - return information about lo = resourceLocks.getLockedObjectByPath(transaction, path); if (lo != null) { generateXMLReport(resp, lo); } else { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { // Locking was not successful sendLockFailError(req, resp); } } private void sendLockFailError( HttpServletRequest req, HttpServletResponse resp ) throws IOException { Hashtable<String, Integer> errorList = new Hashtable<String, Integer>(); errorList.put(path, WebdavStatus.SC_LOCKED); sendReport(req, resp, errorList); } }