/* * 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 com.ejie.x38.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.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import com.ejie.x38.webdav.ITransaction; import com.ejie.x38.webdav.IWebdavStore; import com.ejie.x38.webdav.StoredObject; import com.ejie.x38.webdav.WebdavStatus; import com.ejie.x38.webdav.exceptions.LockFailedException; import com.ejie.x38.webdav.exceptions.WebdavException; import com.ejie.x38.webdav.fromcatalina.XMLWriter; import com.ejie.x38.webdav.locking.IResourceLocks; import com.ejie.x38.webdav.locking.LockedObject; public class DoLock extends AbstractMethod { private static org.slf4j.Logger LOG = org.slf4j.LoggerFactory .getLogger(DoLock.class); private IWebdavStore _store; private IResourceLocks _resourceLocks; private boolean _readOnly; private boolean _macLockRequest = false; private boolean _exclusive = false; private String _type = null; private String _lockOwner = null; private String _path = null; private String _parentPath = null; private String _userAgent = null; public DoLock(IWebdavStore store, IResourceLocks resourceLocks, boolean readOnly) { _store = store; _resourceLocks = resourceLocks; _readOnly = readOnly; } public void execute(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws IOException, LockFailedException { LOG.trace("-- " + this.getClass().getName()); if (_readOnly) { resp.sendError(WebdavStatus.SC_FORBIDDEN); return; } else { _path = getRelativePath(req); _parentPath = getParentPath(getCleanPath(_path)); Hashtable<String, Integer> errorList = new Hashtable<String, Integer>(); if (!checkLocks(transaction, req, resp, _resourceLocks, _path)) { errorList.put(_path, WebdavStatus.SC_LOCKED); sendReport(req, resp, errorList); return; // resource is locked } if (!checkLocks(transaction, req, resp, _resourceLocks, _parentPath)) { errorList.put(_parentPath, WebdavStatus.SC_LOCKED); sendReport(req, resp, errorList); return; // parent is locked } // Mac OS Finder (whether 10.4.x or 10.5) can't store files // because executing a LOCK without lock information causes a // SC_BAD_REQUEST _userAgent = req.getHeader("User-Agent"); if (_userAgent != null && _userAgent.indexOf("Darwin") != -1) { _macLockRequest = true; String timeString = new Long(System.currentTimeMillis()) .toString(); _lockOwner = _userAgent.concat(timeString); } String tempLockOwner = "doLock" + System.currentTimeMillis() + req.toString(); if (_resourceLocks.lock(transaction, _path, tempLockOwner, false, 0, TEMP_TIMEOUT, TEMPORARY)) { try { if (req.getHeader("If") != null) { doRefreshLock(transaction, req, resp); } else { doLock(transaction, req, resp); } } catch (LockFailedException e) { resp.sendError(WebdavStatus.SC_LOCKED); e.printStackTrace(); } finally { _resourceLocks.unlockTemporaryLockedObjects(transaction, _path, tempLockOwner); } } } } private void doLock(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws IOException, LockFailedException { StoredObject so = _store.getStoredObject(transaction, _path); if (so != null) { doLocking(transaction, req, resp); } else { // resource doesn't exist, null-resource lock doNullResourceLock(transaction, req, resp); } so = null; _exclusive = false; _type = null; _lockOwner = null; } private void doLocking(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws IOException { // Tests if LockObject on requested path exists, and if so, tests // exclusivity LockedObject lo = _resourceLocks.getLockedObjectByPath(transaction, _path); if (lo != null) { if (lo.isExclusive()) { sendLockFailError(transaction, req, resp); return; } } try { // Thats the locking itself executeLock(transaction, req, resp); } catch (ServletException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); LOG.trace(e.toString()); } catch (LockFailedException e) { sendLockFailError(transaction, req, resp); } finally { lo = null; } } private void doNullResourceLock(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws IOException { StoredObject parentSo, nullSo = null; try { parentSo = _store.getStoredObject(transaction, _parentPath); if (_parentPath != null && parentSo == null) { _store.createFolder(transaction, _parentPath); } else if (_parentPath != null && parentSo != null && parentSo.isResource()) { resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED); return; } nullSo = _store.getStoredObject(transaction, _path); if (nullSo == null) { // resource doesn't exist _store.createResource(transaction, _path); // Transmit expects 204 response-code, not 201 if (_userAgent != null && _userAgent.indexOf("Transmit") != -1) { LOG .trace("DoLock.execute() : do workaround for user agent '" + _userAgent + "'"); resp.setStatus(WebdavStatus.SC_NO_CONTENT); } else { resp.setStatus(WebdavStatus.SC_CREATED); } } else { // resource already exists, could not execute null-resource lock sendLockFailError(transaction, req, resp); return; } nullSo = _store.getStoredObject(transaction, _path); // define the newly created resource as null-resource nullSo.setNullResource(true); // Thats the locking itself executeLock(transaction, req, resp); } catch (LockFailedException e) { sendLockFailError(transaction, req, resp); } catch (WebdavException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); e.printStackTrace(); } catch (ServletException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); e.printStackTrace(); } finally { parentSo = null; nullSo = null; } } private void doRefreshLock(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws IOException, LockFailedException { String[] lockTokens = getLockIdFromIfHeader(req); String lockToken = null; if (lockTokens != null) lockToken = lockTokens[0]; if (lockToken != null) { // Getting LockObject of specified lockToken in If header LockedObject refreshLo = _resourceLocks.getLockedObjectByID( transaction, lockToken); if (refreshLo != null) { int timeout = getTimeout(transaction, req); refreshLo.refreshTimeout(timeout); // sending success response generateXMLReport(transaction, resp, refreshLo); refreshLo = null; } else { // no LockObject to given lockToken resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED); } } else { resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED); } } // ------------------------------------------------- helper methods /** * Executes the LOCK */ private void executeLock(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws LockFailedException, IOException, ServletException { // Mac OS lock request workaround if (_macLockRequest) { LOG.trace("DoLock.execute() : do workaround for user agent '" + _userAgent + "'"); doMacLockRequestWorkaround(transaction, req, resp); } else { // Getting LockInformation from request if (getLockInformation(transaction, req, resp)) { int depth = getDepth(req); int lockDuration = getTimeout(transaction, req); boolean lockSuccess = false; if (_exclusive) { lockSuccess = _resourceLocks.exclusiveLock(transaction, _path, _lockOwner, depth, lockDuration); } else { lockSuccess = _resourceLocks.sharedLock(transaction, _path, _lockOwner, depth, lockDuration); } if (lockSuccess) { // Locks successfully placed - return information about LockedObject lo = _resourceLocks.getLockedObjectByPath( transaction, _path); if (lo != null) { generateXMLReport(transaction, resp, lo); } else { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { sendLockFailError(transaction, req, resp); throw new LockFailedException(); } } else { // information for LOCK could not be read successfully resp.setContentType("text/xml; charset=UTF-8"); resp.sendError(WebdavStatus.SC_BAD_REQUEST); } } } /** * Tries to get the LockInformation from LOCK request */ private boolean getLockInformation(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Node lockInfoNode = null; DocumentBuilder documentBuilder = null; documentBuilder = getDocumentBuilder(); try { Document document = documentBuilder.parse(new InputSource(req .getInputStream())); // Get the root element of the document Element rootElement = document.getDocumentElement(); lockInfoNode = rootElement; if (lockInfoNode != null) { NodeList childList = lockInfoNode.getChildNodes(); Node lockScopeNode = null; Node lockTypeNode = null; Node lockOwnerNode = null; Node currentNode = null; String nodeName = null; for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE || currentNode.getNodeType() == Node.TEXT_NODE) { nodeName = currentNode.getNodeName(); if (nodeName.endsWith("locktype")) { lockTypeNode = currentNode; } if (nodeName.endsWith("lockscope")) { lockScopeNode = currentNode; } if (nodeName.endsWith("owner")) { lockOwnerNode = currentNode; } } else { return false; } } if (lockScopeNode != null) { String scope = null; childList = lockScopeNode.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE) { scope = currentNode.getNodeName(); if (scope.endsWith("exclusive")) { _exclusive = true; } else if (scope.equals("shared")) { _exclusive = false; } } } if (scope == null) { return false; } } else { return false; } if (lockTypeNode != null) { childList = lockTypeNode.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE) { _type = currentNode.getNodeName(); if (_type.endsWith("write")) { _type = "write"; } else if (_type.equals("read")) { _type = "read"; } } } if (_type == null) { return false; } } else { return false; } if (lockOwnerNode != null) { childList = lockOwnerNode.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { currentNode = childList.item(i); if (currentNode.getNodeType() == Node.ELEMENT_NODE || currentNode.getNodeType() == Node.TEXT_NODE ) { _lockOwner = currentNode.getTextContent(); } } } if (_lockOwner == null) { return false; } } else { return false; } } catch (DOMException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); e.printStackTrace(); return false; } catch (SAXException e) { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); e.printStackTrace(); return false; } return true; } /** * Ties to read the timeout from request */ private int getTimeout(ITransaction transaction, HttpServletRequest req) { int lockDuration = DEFAULT_TIMEOUT; String lockDurationStr = req.getHeader("Timeout"); if (lockDurationStr == null) { lockDuration = DEFAULT_TIMEOUT; } else { int commaPos = lockDurationStr.indexOf(','); // if multiple timeouts, just use the first one if (commaPos != -1) { lockDurationStr = lockDurationStr.substring(0, commaPos); } if (lockDurationStr.startsWith("Second-")) { lockDuration = new Integer(lockDurationStr.substring(7)) .intValue(); } else { if (lockDurationStr.equalsIgnoreCase("infinity")) { lockDuration = MAX_TIMEOUT; } else { try { lockDuration = new Integer(lockDurationStr).intValue(); } catch (NumberFormatException e) { lockDuration = MAX_TIMEOUT; } } } if (lockDuration <= 0) { lockDuration = DEFAULT_TIMEOUT; } if (lockDuration > MAX_TIMEOUT) { lockDuration = MAX_TIMEOUT; } } return lockDuration; } /** * Generates the response XML with all lock information */ private void generateXMLReport(ITransaction transaction, HttpServletResponse resp, LockedObject lo) throws IOException { HashMap<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 = lo.getLockDepth(); generatedXML.writeElement("DAV::depth", XMLWriter.OPENING); if (depth == INFINITY) { generatedXML.writeText("Infinity"); } else { generatedXML.writeText(String.valueOf(depth)); } generatedXML.writeElement("DAV::depth", XMLWriter.CLOSING); generatedXML.writeElement("DAV::owner", XMLWriter.OPENING); // encapsulating the owner with an href-element will trigger the bug // generatedXML.writeElement("DAV::href", XMLWriter.OPENING); generatedXML.writeText(_lockOwner); // generatedXML.writeElement("DAV::href", XMLWriter.CLOSING); generatedXML.writeElement("DAV::owner", XMLWriter.CLOSING); long timeout = lo.getTimeoutMillis(); generatedXML.writeElement("DAV::timeout", XMLWriter.OPENING); generatedXML.writeText("Second-" + timeout / 1000); generatedXML.writeElement("DAV::timeout", XMLWriter.CLOSING); String lockToken = lo.getID(); generatedXML.writeElement("DAV::locktoken", XMLWriter.OPENING); generatedXML.writeElement("DAV::href", XMLWriter.OPENING); generatedXML.writeText("opaquelocktoken:" + lockToken); generatedXML.writeElement("DAV::href", XMLWriter.CLOSING); generatedXML.writeElement("DAV::locktoken", XMLWriter.CLOSING); generatedXML.writeElement("DAV::activelock", XMLWriter.CLOSING); generatedXML.writeElement("DAV::lockdiscovery", XMLWriter.CLOSING); generatedXML.writeElement("DAV::prop", XMLWriter.CLOSING); resp.addHeader("Lock-Token", "<opaquelocktoken:" + lockToken + ">"); generatedXML.sendData(); } /** * Executes the lock for a Mac OS Finder client */ private void doMacLockRequestWorkaround(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws LockFailedException, IOException { LockedObject lo; int depth = getDepth(req); int lockDuration = getTimeout(transaction, req); if (lockDuration < 0 || lockDuration > MAX_TIMEOUT) lockDuration = DEFAULT_TIMEOUT; boolean lockSuccess = false; lockSuccess = _resourceLocks.exclusiveLock(transaction, _path, _lockOwner, depth, lockDuration); if (lockSuccess) { // Locks successfully placed - return information about lo = _resourceLocks.getLockedObjectByPath(transaction, _path); if (lo != null) { generateXMLReport(transaction, resp, lo); } else { resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR); } } else { // Locking was not successful sendLockFailError(transaction, req, resp); } } /** * Sends an error report to the client */ private void sendLockFailError(ITransaction transaction, HttpServletRequest req, HttpServletResponse resp) throws IOException { Hashtable<String, Integer> errorList = new Hashtable<String, Integer>(); errorList.put(_path, WebdavStatus.SC_LOCKED); sendReport(req, resp, errorList); } }