0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.catalina.servlets;
0019:
0020: import java.io.IOException;
0021: import java.io.StringWriter;
0022: import java.io.Writer;
0023: import java.security.MessageDigest;
0024: import java.security.NoSuchAlgorithmException;
0025: import java.text.SimpleDateFormat;
0026: import java.util.Date;
0027: import java.util.Enumeration;
0028: import java.util.Hashtable;
0029: import java.util.Stack;
0030: import java.util.TimeZone;
0031: import java.util.Vector;
0032:
0033: import javax.naming.NameClassPair;
0034: import javax.naming.NamingEnumeration;
0035: import javax.naming.NamingException;
0036: import javax.naming.directory.DirContext;
0037: import javax.servlet.ServletException;
0038: import javax.servlet.UnavailableException;
0039: import javax.servlet.http.HttpServletRequest;
0040: import javax.servlet.http.HttpServletResponse;
0041: import javax.xml.parsers.DocumentBuilder;
0042: import javax.xml.parsers.DocumentBuilderFactory;
0043: import javax.xml.parsers.ParserConfigurationException;
0044:
0045: import org.apache.catalina.util.DOMWriter;
0046: import org.apache.catalina.util.MD5Encoder;
0047: import org.apache.catalina.util.RequestUtil;
0048: import org.apache.catalina.util.XMLWriter;
0049: import org.apache.naming.resources.CacheEntry;
0050: import org.apache.naming.resources.Resource;
0051: import org.apache.naming.resources.ResourceAttributes;
0052: import org.apache.tomcat.util.http.FastHttpDateFormat;
0053: import org.w3c.dom.Document;
0054: import org.w3c.dom.Element;
0055: import org.w3c.dom.Node;
0056: import org.w3c.dom.NodeList;
0057: import org.xml.sax.InputSource;
0058: import org.xml.sax.SAXException;
0059:
0060: /**
0061: * Servlet which adds support for WebDAV level 2. All the basic HTTP requests
0062: * are handled by the DefaultServlet.
0063: *
0064: * @author Remy Maucherat
0065: * @version $Revision: 543681 $ $Date: 2007-06-02 02:42:59 +0200 (sam., 02 juin 2007) $
0066: */
0067:
0068: public class WebdavServlet extends DefaultServlet {
0069:
0070: // -------------------------------------------------------------- Constants
0071:
0072: private static final String METHOD_HEAD = "HEAD";
0073: private static final String METHOD_PROPFIND = "PROPFIND";
0074: private static final String METHOD_PROPPATCH = "PROPPATCH";
0075: private static final String METHOD_MKCOL = "MKCOL";
0076: private static final String METHOD_COPY = "COPY";
0077: private static final String METHOD_MOVE = "MOVE";
0078: private static final String METHOD_LOCK = "LOCK";
0079: private static final String METHOD_UNLOCK = "UNLOCK";
0080:
0081: /**
0082: * Default depth is infite.
0083: */
0084: private static final int INFINITY = 3; // To limit tree browsing a bit
0085:
0086: /**
0087: * PROPFIND - Specify a property mask.
0088: */
0089: private static final int FIND_BY_PROPERTY = 0;
0090:
0091: /**
0092: * PROPFIND - Display all properties.
0093: */
0094: private static final int FIND_ALL_PROP = 1;
0095:
0096: /**
0097: * PROPFIND - Return property names.
0098: */
0099: private static final int FIND_PROPERTY_NAMES = 2;
0100:
0101: /**
0102: * Create a new lock.
0103: */
0104: private static final int LOCK_CREATION = 0;
0105:
0106: /**
0107: * Refresh lock.
0108: */
0109: private static final int LOCK_REFRESH = 1;
0110:
0111: /**
0112: * Default lock timeout value.
0113: */
0114: private static final int DEFAULT_TIMEOUT = 3600;
0115:
0116: /**
0117: * Maximum lock timeout.
0118: */
0119: private static final int MAX_TIMEOUT = 604800;
0120:
0121: /**
0122: * Default namespace.
0123: */
0124: protected static final String DEFAULT_NAMESPACE = "DAV:";
0125:
0126: /**
0127: * Simple date format for the creation date ISO representation (partial).
0128: */
0129: protected static final SimpleDateFormat creationDateFormat = new SimpleDateFormat(
0130: "yyyy-MM-dd'T'HH:mm:ss'Z'");
0131:
0132: /**
0133: * MD5 message digest provider.
0134: */
0135: protected static MessageDigest md5Helper;
0136:
0137: /**
0138: * The MD5 helper object for this class.
0139: */
0140: protected static final MD5Encoder md5Encoder = new MD5Encoder();
0141:
0142: static {
0143: creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
0144: }
0145:
0146: // ----------------------------------------------------- Instance Variables
0147:
0148: /**
0149: * Repository of the locks put on single resources.
0150: * <p>
0151: * Key : path <br>
0152: * Value : LockInfo
0153: */
0154: private Hashtable<String, LockInfo> resourceLocks = new Hashtable<String, LockInfo>();
0155:
0156: /**
0157: * Repository of the lock-null resources.
0158: * <p>
0159: * Key : path of the collection containing the lock-null resource<br>
0160: * Value : Vector of lock-null resource which are members of the
0161: * collection. Each element of the Vector is the path associated with
0162: * the lock-null resource.
0163: */
0164: private Hashtable<String, Vector<String>> lockNullResources = new Hashtable<String, Vector<String>>();
0165:
0166: /**
0167: * Vector of the heritable locks.
0168: * <p>
0169: * Key : path <br>
0170: * Value : LockInfo
0171: */
0172: private Vector<LockInfo> collectionLocks = new Vector<LockInfo>();
0173:
0174: /**
0175: * Secret information used to generate reasonably secure lock ids.
0176: */
0177: private String secret = "catalina";
0178:
0179: // --------------------------------------------------------- Public Methods
0180:
0181: /**
0182: * Initialize this servlet.
0183: */
0184: public void init() throws ServletException {
0185:
0186: super .init();
0187:
0188: if (getServletConfig().getInitParameter("secret") != null)
0189: secret = getServletConfig().getInitParameter("secret");
0190:
0191: // Load the MD5 helper used to calculate signatures.
0192: try {
0193: md5Helper = MessageDigest.getInstance("MD5");
0194: } catch (NoSuchAlgorithmException e) {
0195: throw new UnavailableException("No MD5");
0196: }
0197:
0198: }
0199:
0200: // ------------------------------------------------------ Protected Methods
0201:
0202: /**
0203: * Return JAXP document builder instance.
0204: */
0205: protected DocumentBuilder getDocumentBuilder()
0206: throws ServletException {
0207: DocumentBuilder documentBuilder = null;
0208: DocumentBuilderFactory documentBuilderFactory = null;
0209: try {
0210: documentBuilderFactory = DocumentBuilderFactory
0211: .newInstance();
0212: documentBuilderFactory.setNamespaceAware(true);
0213: documentBuilder = documentBuilderFactory
0214: .newDocumentBuilder();
0215: } catch (ParserConfigurationException e) {
0216: throw new ServletException(sm
0217: .getString("webdavservlet.jaxpfailed"));
0218: }
0219: return documentBuilder;
0220: }
0221:
0222: /**
0223: * Handles the special WebDAV methods.
0224: */
0225: protected void service(HttpServletRequest req,
0226: HttpServletResponse resp) throws ServletException,
0227: IOException {
0228:
0229: String method = req.getMethod();
0230:
0231: if (debug > 0) {
0232: String path = getRelativePath(req);
0233: log("[" + method + "] " + path);
0234: }
0235:
0236: if (method.equals(METHOD_PROPFIND)) {
0237: doPropfind(req, resp);
0238: } else if (method.equals(METHOD_PROPPATCH)) {
0239: doProppatch(req, resp);
0240: } else if (method.equals(METHOD_MKCOL)) {
0241: doMkcol(req, resp);
0242: } else if (method.equals(METHOD_COPY)) {
0243: doCopy(req, resp);
0244: } else if (method.equals(METHOD_MOVE)) {
0245: doMove(req, resp);
0246: } else if (method.equals(METHOD_LOCK)) {
0247: doLock(req, resp);
0248: } else if (method.equals(METHOD_UNLOCK)) {
0249: doUnlock(req, resp);
0250: } else {
0251: // DefaultServlet processing
0252: super .service(req, resp);
0253: }
0254:
0255: }
0256:
0257: /**
0258: * Check if the conditions specified in the optional If headers are
0259: * satisfied.
0260: *
0261: * @param request The servlet request we are processing
0262: * @param response The servlet response we are creating
0263: * @param resourceAttributes The resource information
0264: * @return boolean true if the resource meets all the specified conditions,
0265: * and false if any of the conditions is not satisfied, in which case
0266: * request processing is stopped
0267: */
0268: protected boolean checkIfHeaders(HttpServletRequest request,
0269: HttpServletResponse response,
0270: ResourceAttributes resourceAttributes) throws IOException {
0271:
0272: if (!super
0273: .checkIfHeaders(request, response, resourceAttributes))
0274: return false;
0275:
0276: // TODO : Checking the WebDAV If header
0277: return true;
0278:
0279: }
0280:
0281: /**
0282: * OPTIONS Method.
0283: *
0284: * @param req The request
0285: * @param resp The response
0286: * @throws ServletException If an error occurs
0287: * @throws IOException If an IO error occurs
0288: */
0289: protected void doOptions(HttpServletRequest req,
0290: HttpServletResponse resp) throws ServletException,
0291: IOException {
0292:
0293: resp.addHeader("DAV", "1,2");
0294:
0295: StringBuffer methodsAllowed = determineMethodsAllowed(
0296: resources, req);
0297:
0298: resp.addHeader("Allow", methodsAllowed.toString());
0299: resp.addHeader("MS-Author-Via", "DAV");
0300:
0301: }
0302:
0303: /**
0304: * PROPFIND Method.
0305: */
0306: protected void doPropfind(HttpServletRequest req,
0307: HttpServletResponse resp) throws ServletException,
0308: IOException {
0309:
0310: if (!listings) {
0311: // Get allowed methods
0312: StringBuffer methodsAllowed = determineMethodsAllowed(
0313: resources, req);
0314:
0315: resp.addHeader("Allow", methodsAllowed.toString());
0316: resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
0317: return;
0318: }
0319:
0320: String path = getRelativePath(req);
0321: if (path.endsWith("/"))
0322: path = path.substring(0, path.length() - 1);
0323:
0324: if ((path.toUpperCase().startsWith("/WEB-INF"))
0325: || (path.toUpperCase().startsWith("/META-INF"))) {
0326: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0327: return;
0328: }
0329:
0330: // Properties which are to be displayed.
0331: Vector<String> properties = null;
0332: // Propfind depth
0333: int depth = INFINITY;
0334: // Propfind type
0335: int type = FIND_ALL_PROP;
0336:
0337: String depthStr = req.getHeader("Depth");
0338:
0339: if (depthStr == null) {
0340: depth = INFINITY;
0341: } else {
0342: if (depthStr.equals("0")) {
0343: depth = 0;
0344: } else if (depthStr.equals("1")) {
0345: depth = 1;
0346: } else if (depthStr.equals("infinity")) {
0347: depth = INFINITY;
0348: }
0349: }
0350:
0351: Node propNode = null;
0352:
0353: DocumentBuilder documentBuilder = getDocumentBuilder();
0354:
0355: try {
0356: Document document = documentBuilder.parse(new InputSource(
0357: req.getInputStream()));
0358:
0359: // Get the root element of the document
0360: Element rootElement = document.getDocumentElement();
0361: NodeList childList = rootElement.getChildNodes();
0362:
0363: for (int i = 0; i < childList.getLength(); i++) {
0364: Node currentNode = childList.item(i);
0365: switch (currentNode.getNodeType()) {
0366: case Node.TEXT_NODE:
0367: break;
0368: case Node.ELEMENT_NODE:
0369: if (currentNode.getNodeName().endsWith("prop")) {
0370: type = FIND_BY_PROPERTY;
0371: propNode = currentNode;
0372: }
0373: if (currentNode.getNodeName().endsWith("propname")) {
0374: type = FIND_PROPERTY_NAMES;
0375: }
0376: if (currentNode.getNodeName().endsWith("allprop")) {
0377: type = FIND_ALL_PROP;
0378: }
0379: break;
0380: }
0381: }
0382: } catch (SAXException e) {
0383: // Most likely there was no content : we use the defaults.
0384: } catch (IOException e) {
0385: // Most likely there was no content : we use the defaults.
0386: }
0387:
0388: if (type == FIND_BY_PROPERTY) {
0389: properties = new Vector<String>();
0390: NodeList childList = propNode.getChildNodes();
0391:
0392: for (int i = 0; i < childList.getLength(); i++) {
0393: Node currentNode = childList.item(i);
0394: switch (currentNode.getNodeType()) {
0395: case Node.TEXT_NODE:
0396: break;
0397: case Node.ELEMENT_NODE:
0398: String nodeName = currentNode.getNodeName();
0399: String propertyName = null;
0400: if (nodeName.indexOf(':') != -1) {
0401: propertyName = nodeName.substring(nodeName
0402: .indexOf(':') + 1);
0403: } else {
0404: propertyName = nodeName;
0405: }
0406: // href is a live property which is handled differently
0407: properties.addElement(propertyName);
0408: break;
0409: }
0410: }
0411:
0412: }
0413:
0414: boolean exists = true;
0415: Object object = null;
0416: try {
0417: object = resources.lookup(path);
0418: } catch (NamingException e) {
0419: exists = false;
0420: int slash = path.lastIndexOf('/');
0421: if (slash != -1) {
0422: String parentPath = path.substring(0, slash);
0423: Vector currentLockNullResources = (Vector) lockNullResources
0424: .get(parentPath);
0425: if (currentLockNullResources != null) {
0426: Enumeration lockNullResourcesList = currentLockNullResources
0427: .elements();
0428: while (lockNullResourcesList.hasMoreElements()) {
0429: String lockNullPath = (String) lockNullResourcesList
0430: .nextElement();
0431: if (lockNullPath.equals(path)) {
0432: resp
0433: .setStatus(WebdavStatus.SC_MULTI_STATUS);
0434: resp
0435: .setContentType("text/xml; charset=UTF-8");
0436: // Create multistatus object
0437: XMLWriter generatedXML = new XMLWriter(resp
0438: .getWriter());
0439: generatedXML.writeXMLHeader();
0440: generatedXML
0441: .writeElement(
0442: null,
0443: "multistatus"
0444: + generateNamespaceDeclarations(),
0445: XMLWriter.OPENING);
0446: parseLockNullProperties(req, generatedXML,
0447: lockNullPath, type, properties);
0448: generatedXML.writeElement(null,
0449: "multistatus", XMLWriter.CLOSING);
0450: generatedXML.sendData();
0451: return;
0452: }
0453: }
0454: }
0455: }
0456: }
0457:
0458: if (!exists) {
0459: resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
0460: return;
0461: }
0462:
0463: resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
0464:
0465: resp.setContentType("text/xml; charset=UTF-8");
0466:
0467: // Create multistatus object
0468: XMLWriter generatedXML = new XMLWriter(resp.getWriter());
0469: generatedXML.writeXMLHeader();
0470:
0471: generatedXML.writeElement(null, "multistatus"
0472: + generateNamespaceDeclarations(), XMLWriter.OPENING);
0473:
0474: if (depth == 0) {
0475: parseProperties(req, generatedXML, path, type, properties);
0476: } else {
0477: // The stack always contains the object of the current level
0478: Stack<String> stack = new Stack<String>();
0479: stack.push(path);
0480:
0481: // Stack of the objects one level below
0482: Stack<String> stackBelow = new Stack<String>();
0483:
0484: while ((!stack.isEmpty()) && (depth >= 0)) {
0485:
0486: String currentPath = (String) stack.pop();
0487: parseProperties(req, generatedXML, currentPath, type,
0488: properties);
0489:
0490: try {
0491: object = resources.lookup(currentPath);
0492: } catch (NamingException e) {
0493: continue;
0494: }
0495:
0496: if ((object instanceof DirContext) && (depth > 0)) {
0497:
0498: try {
0499: NamingEnumeration enumeration = resources
0500: .list(currentPath);
0501: while (enumeration.hasMoreElements()) {
0502: NameClassPair ncPair = (NameClassPair) enumeration
0503: .nextElement();
0504: String newPath = currentPath;
0505: if (!(newPath.endsWith("/")))
0506: newPath += "/";
0507: newPath += ncPair.getName();
0508: stackBelow.push(newPath);
0509: }
0510: } catch (NamingException e) {
0511: resp
0512: .sendError(
0513: HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
0514: path);
0515: return;
0516: }
0517:
0518: // Displaying the lock-null resources present in that
0519: // collection
0520: String lockPath = currentPath;
0521: if (lockPath.endsWith("/"))
0522: lockPath = lockPath.substring(0, lockPath
0523: .length() - 1);
0524: Vector currentLockNullResources = (Vector) lockNullResources
0525: .get(lockPath);
0526: if (currentLockNullResources != null) {
0527: Enumeration lockNullResourcesList = currentLockNullResources
0528: .elements();
0529: while (lockNullResourcesList.hasMoreElements()) {
0530: String lockNullPath = (String) lockNullResourcesList
0531: .nextElement();
0532: parseLockNullProperties(req, generatedXML,
0533: lockNullPath, type, properties);
0534: }
0535: }
0536:
0537: }
0538:
0539: if (stack.isEmpty()) {
0540: depth--;
0541: stack = stackBelow;
0542: stackBelow = new Stack<String>();
0543: }
0544:
0545: generatedXML.sendData();
0546:
0547: }
0548: }
0549:
0550: generatedXML.writeElement(null, "multistatus",
0551: XMLWriter.CLOSING);
0552:
0553: generatedXML.sendData();
0554:
0555: }
0556:
0557: /**
0558: * PROPPATCH Method.
0559: */
0560: protected void doProppatch(HttpServletRequest req,
0561: HttpServletResponse resp) throws ServletException,
0562: IOException {
0563:
0564: if (readOnly) {
0565: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0566: return;
0567: }
0568:
0569: if (isLocked(req)) {
0570: resp.sendError(WebdavStatus.SC_LOCKED);
0571: return;
0572: }
0573:
0574: resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
0575:
0576: }
0577:
0578: /**
0579: * MKCOL Method.
0580: */
0581: protected void doMkcol(HttpServletRequest req,
0582: HttpServletResponse resp) throws ServletException,
0583: IOException {
0584:
0585: if (readOnly) {
0586: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0587: return;
0588: }
0589:
0590: if (isLocked(req)) {
0591: resp.sendError(WebdavStatus.SC_LOCKED);
0592: return;
0593: }
0594:
0595: String path = getRelativePath(req);
0596:
0597: if ((path.toUpperCase().startsWith("/WEB-INF"))
0598: || (path.toUpperCase().startsWith("/META-INF"))) {
0599: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0600: return;
0601: }
0602:
0603: boolean exists = true;
0604: Object object = null;
0605: try {
0606: object = resources.lookup(path);
0607: } catch (NamingException e) {
0608: exists = false;
0609: }
0610:
0611: // Can't create a collection if a resource already exists at the given
0612: // path
0613: if (exists) {
0614: // Get allowed methods
0615: StringBuffer methodsAllowed = determineMethodsAllowed(
0616: resources, req);
0617:
0618: resp.addHeader("Allow", methodsAllowed.toString());
0619:
0620: resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
0621: return;
0622: }
0623:
0624: if (req.getInputStream().available() > 0) {
0625: DocumentBuilder documentBuilder = getDocumentBuilder();
0626: try {
0627: Document document = documentBuilder
0628: .parse(new InputSource(req.getInputStream()));
0629: // TODO : Process this request body
0630: resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
0631: return;
0632:
0633: } catch (SAXException saxe) {
0634: // Parse error - assume invalid content
0635: resp.sendError(WebdavStatus.SC_BAD_REQUEST);
0636: return;
0637: }
0638: }
0639:
0640: boolean result = true;
0641: try {
0642: resources.createSubcontext(path);
0643: } catch (NamingException e) {
0644: result = false;
0645: }
0646:
0647: if (!result) {
0648: resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus
0649: .getStatusText(WebdavStatus.SC_CONFLICT));
0650: } else {
0651: resp.setStatus(WebdavStatus.SC_CREATED);
0652: // Removing any lock-null resource which would be present
0653: lockNullResources.remove(path);
0654: }
0655:
0656: }
0657:
0658: /**
0659: * DELETE Method.
0660: */
0661: protected void doDelete(HttpServletRequest req,
0662: HttpServletResponse resp) throws ServletException,
0663: IOException {
0664:
0665: if (readOnly) {
0666: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0667: return;
0668: }
0669:
0670: if (isLocked(req)) {
0671: resp.sendError(WebdavStatus.SC_LOCKED);
0672: return;
0673: }
0674:
0675: deleteResource(req, resp);
0676:
0677: }
0678:
0679: /**
0680: * Process a POST request for the specified resource.
0681: *
0682: * @param req The servlet request we are processing
0683: * @param resp The servlet response we are creating
0684: *
0685: * @exception IOException if an input/output error occurs
0686: * @exception ServletException if a servlet-specified error occurs
0687: */
0688: protected void doPut(HttpServletRequest req,
0689: HttpServletResponse resp) throws ServletException,
0690: IOException {
0691:
0692: if (isLocked(req)) {
0693: resp.sendError(WebdavStatus.SC_LOCKED);
0694: return;
0695: }
0696:
0697: super .doPut(req, resp);
0698:
0699: String path = getRelativePath(req);
0700:
0701: // Removing any lock-null resource which would be present
0702: lockNullResources.remove(path);
0703:
0704: }
0705:
0706: /**
0707: * COPY Method.
0708: */
0709: protected void doCopy(HttpServletRequest req,
0710: HttpServletResponse resp) throws ServletException,
0711: IOException {
0712:
0713: if (readOnly) {
0714: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0715: return;
0716: }
0717:
0718: copyResource(req, resp);
0719:
0720: }
0721:
0722: /**
0723: * MOVE Method.
0724: */
0725: protected void doMove(HttpServletRequest req,
0726: HttpServletResponse resp) throws ServletException,
0727: IOException {
0728:
0729: if (readOnly) {
0730: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0731: return;
0732: }
0733:
0734: if (isLocked(req)) {
0735: resp.sendError(WebdavStatus.SC_LOCKED);
0736: return;
0737: }
0738:
0739: String path = getRelativePath(req);
0740:
0741: if (copyResource(req, resp)) {
0742: deleteResource(path, req, resp, false);
0743: }
0744:
0745: }
0746:
0747: /**
0748: * LOCK Method.
0749: */
0750: protected void doLock(HttpServletRequest req,
0751: HttpServletResponse resp) throws ServletException,
0752: IOException {
0753:
0754: if (readOnly) {
0755: resp.sendError(WebdavStatus.SC_FORBIDDEN);
0756: return;
0757: }
0758:
0759: if (isLocked(req)) {
0760: resp.sendError(WebdavStatus.SC_LOCKED);
0761: return;
0762: }
0763:
0764: LockInfo lock = new LockInfo();
0765:
0766: // Parsing lock request
0767:
0768: // Parsing depth header
0769:
0770: String depthStr = req.getHeader("Depth");
0771:
0772: if (depthStr == null) {
0773: lock.depth = INFINITY;
0774: } else {
0775: if (depthStr.equals("0")) {
0776: lock.depth = 0;
0777: } else {
0778: lock.depth = INFINITY;
0779: }
0780: }
0781:
0782: // Parsing timeout header
0783:
0784: int lockDuration = DEFAULT_TIMEOUT;
0785: String lockDurationStr = req.getHeader("Timeout");
0786: if (lockDurationStr == null) {
0787: lockDuration = DEFAULT_TIMEOUT;
0788: } else {
0789: int commaPos = lockDurationStr.indexOf(",");
0790: // If multiple timeouts, just use the first
0791: if (commaPos != -1) {
0792: lockDurationStr = lockDurationStr
0793: .substring(0, commaPos);
0794: }
0795: if (lockDurationStr.startsWith("Second-")) {
0796: lockDuration = (new Integer(lockDurationStr
0797: .substring(7))).intValue();
0798: } else {
0799: if (lockDurationStr.equalsIgnoreCase("infinity")) {
0800: lockDuration = MAX_TIMEOUT;
0801: } else {
0802: try {
0803: lockDuration = (new Integer(lockDurationStr))
0804: .intValue();
0805: } catch (NumberFormatException e) {
0806: lockDuration = MAX_TIMEOUT;
0807: }
0808: }
0809: }
0810: if (lockDuration == 0) {
0811: lockDuration = DEFAULT_TIMEOUT;
0812: }
0813: if (lockDuration > MAX_TIMEOUT) {
0814: lockDuration = MAX_TIMEOUT;
0815: }
0816: }
0817: lock.expiresAt = System.currentTimeMillis()
0818: + (lockDuration * 1000);
0819:
0820: int lockRequestType = LOCK_CREATION;
0821:
0822: Node lockInfoNode = null;
0823:
0824: DocumentBuilder documentBuilder = getDocumentBuilder();
0825:
0826: try {
0827: Document document = documentBuilder.parse(new InputSource(
0828: req.getInputStream()));
0829:
0830: // Get the root element of the document
0831: Element rootElement = document.getDocumentElement();
0832: lockInfoNode = rootElement;
0833: } catch (IOException e) {
0834: lockRequestType = LOCK_REFRESH;
0835: } catch (SAXException e) {
0836: lockRequestType = LOCK_REFRESH;
0837: }
0838:
0839: if (lockInfoNode != null) {
0840:
0841: // Reading lock information
0842:
0843: NodeList childList = lockInfoNode.getChildNodes();
0844: StringWriter strWriter = null;
0845: DOMWriter domWriter = null;
0846:
0847: Node lockScopeNode = null;
0848: Node lockTypeNode = null;
0849: Node lockOwnerNode = null;
0850:
0851: for (int i = 0; i < childList.getLength(); i++) {
0852: Node currentNode = childList.item(i);
0853: switch (currentNode.getNodeType()) {
0854: case Node.TEXT_NODE:
0855: break;
0856: case Node.ELEMENT_NODE:
0857: String nodeName = currentNode.getNodeName();
0858: if (nodeName.endsWith("lockscope")) {
0859: lockScopeNode = currentNode;
0860: }
0861: if (nodeName.endsWith("locktype")) {
0862: lockTypeNode = currentNode;
0863: }
0864: if (nodeName.endsWith("owner")) {
0865: lockOwnerNode = currentNode;
0866: }
0867: break;
0868: }
0869: }
0870:
0871: if (lockScopeNode != null) {
0872:
0873: childList = lockScopeNode.getChildNodes();
0874: for (int i = 0; i < childList.getLength(); i++) {
0875: Node currentNode = childList.item(i);
0876: switch (currentNode.getNodeType()) {
0877: case Node.TEXT_NODE:
0878: break;
0879: case Node.ELEMENT_NODE:
0880: String tempScope = currentNode.getNodeName();
0881: if (tempScope.indexOf(':') != -1) {
0882: lock.scope = tempScope.substring(tempScope
0883: .indexOf(':') + 1);
0884: } else {
0885: lock.scope = tempScope;
0886: }
0887: break;
0888: }
0889: }
0890:
0891: if (lock.scope == null) {
0892: // Bad request
0893: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
0894: }
0895:
0896: } else {
0897: // Bad request
0898: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
0899: }
0900:
0901: if (lockTypeNode != null) {
0902:
0903: childList = lockTypeNode.getChildNodes();
0904: for (int i = 0; i < childList.getLength(); i++) {
0905: Node currentNode = childList.item(i);
0906: switch (currentNode.getNodeType()) {
0907: case Node.TEXT_NODE:
0908: break;
0909: case Node.ELEMENT_NODE:
0910: String tempType = currentNode.getNodeName();
0911: if (tempType.indexOf(':') != -1) {
0912: lock.type = tempType.substring(tempType
0913: .indexOf(':') + 1);
0914: } else {
0915: lock.type = tempType;
0916: }
0917: break;
0918: }
0919: }
0920:
0921: if (lock.type == null) {
0922: // Bad request
0923: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
0924: }
0925:
0926: } else {
0927: // Bad request
0928: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
0929: }
0930:
0931: if (lockOwnerNode != null) {
0932:
0933: childList = lockOwnerNode.getChildNodes();
0934: for (int i = 0; i < childList.getLength(); i++) {
0935: Node currentNode = childList.item(i);
0936: switch (currentNode.getNodeType()) {
0937: case Node.TEXT_NODE:
0938: lock.owner += currentNode.getNodeValue();
0939: break;
0940: case Node.ELEMENT_NODE:
0941: strWriter = new StringWriter();
0942: domWriter = new DOMWriter(strWriter, true);
0943: domWriter.setQualifiedNames(false);
0944: domWriter.print(currentNode);
0945: lock.owner += strWriter.toString();
0946: break;
0947: }
0948: }
0949:
0950: if (lock.owner == null) {
0951: // Bad request
0952: resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
0953: }
0954:
0955: } else {
0956: lock.owner = new String();
0957: }
0958:
0959: }
0960:
0961: String path = getRelativePath(req);
0962:
0963: lock.path = path;
0964:
0965: boolean exists = true;
0966: Object object = null;
0967: try {
0968: object = resources.lookup(path);
0969: } catch (NamingException e) {
0970: exists = false;
0971: }
0972:
0973: Enumeration locksList = null;
0974:
0975: if (lockRequestType == LOCK_CREATION) {
0976:
0977: // Generating lock id
0978: String lockTokenStr = req.getServletPath() + "-"
0979: + lock.type + "-" + lock.scope + "-"
0980: + req.getUserPrincipal() + "-" + lock.depth + "-"
0981: + lock.owner + "-" + lock.tokens + "-"
0982: + lock.expiresAt + "-" + System.currentTimeMillis()
0983: + "-" + secret;
0984: String lockToken = md5Encoder.encode(md5Helper
0985: .digest(lockTokenStr.getBytes()));
0986:
0987: if ((exists) && (object instanceof DirContext)
0988: && (lock.depth == INFINITY)) {
0989:
0990: // Locking a collection (and all its member resources)
0991:
0992: // Checking if a child resource of this collection is
0993: // already locked
0994: Vector<String> lockPaths = new Vector<String>();
0995: locksList = collectionLocks.elements();
0996: while (locksList.hasMoreElements()) {
0997: LockInfo currentLock = (LockInfo) locksList
0998: .nextElement();
0999: if (currentLock.hasExpired()) {
1000: resourceLocks.remove(currentLock.path);
1001: continue;
1002: }
1003: if ((currentLock.path.startsWith(lock.path))
1004: && ((currentLock.isExclusive()) || (lock
1005: .isExclusive()))) {
1006: // A child collection of this collection is locked
1007: lockPaths.addElement(currentLock.path);
1008: }
1009: }
1010: locksList = resourceLocks.elements();
1011: while (locksList.hasMoreElements()) {
1012: LockInfo currentLock = (LockInfo) locksList
1013: .nextElement();
1014: if (currentLock.hasExpired()) {
1015: resourceLocks.remove(currentLock.path);
1016: continue;
1017: }
1018: if ((currentLock.path.startsWith(lock.path))
1019: && ((currentLock.isExclusive()) || (lock
1020: .isExclusive()))) {
1021: // A child resource of this collection is locked
1022: lockPaths.addElement(currentLock.path);
1023: }
1024: }
1025:
1026: if (!lockPaths.isEmpty()) {
1027:
1028: // One of the child paths was locked
1029: // We generate a multistatus error report
1030:
1031: Enumeration lockPathsList = lockPaths.elements();
1032:
1033: resp.setStatus(WebdavStatus.SC_CONFLICT);
1034:
1035: XMLWriter generatedXML = new XMLWriter();
1036: generatedXML.writeXMLHeader();
1037:
1038: generatedXML.writeElement(null, "multistatus"
1039: + generateNamespaceDeclarations(),
1040: XMLWriter.OPENING);
1041:
1042: while (lockPathsList.hasMoreElements()) {
1043: generatedXML.writeElement(null, "response",
1044: XMLWriter.OPENING);
1045: generatedXML.writeElement(null, "href",
1046: XMLWriter.OPENING);
1047: generatedXML.writeText((String) lockPathsList
1048: .nextElement());
1049: generatedXML.writeElement(null, "href",
1050: XMLWriter.CLOSING);
1051: generatedXML.writeElement(null, "status",
1052: XMLWriter.OPENING);
1053: generatedXML
1054: .writeText("HTTP/1.1 "
1055: + WebdavStatus.SC_LOCKED
1056: + " "
1057: + WebdavStatus
1058: .getStatusText(WebdavStatus.SC_LOCKED));
1059: generatedXML.writeElement(null, "status",
1060: XMLWriter.CLOSING);
1061:
1062: generatedXML.writeElement(null, "response",
1063: XMLWriter.CLOSING);
1064: }
1065:
1066: generatedXML.writeElement(null, "multistatus",
1067: XMLWriter.CLOSING);
1068:
1069: Writer writer = resp.getWriter();
1070: writer.write(generatedXML.toString());
1071: writer.close();
1072:
1073: return;
1074:
1075: }
1076:
1077: boolean addLock = true;
1078:
1079: // Checking if there is already a shared lock on this path
1080: locksList = collectionLocks.elements();
1081: while (locksList.hasMoreElements()) {
1082:
1083: LockInfo currentLock = (LockInfo) locksList
1084: .nextElement();
1085: if (currentLock.path.equals(lock.path)) {
1086:
1087: if (currentLock.isExclusive()) {
1088: resp.sendError(WebdavStatus.SC_LOCKED);
1089: return;
1090: } else {
1091: if (lock.isExclusive()) {
1092: resp.sendError(WebdavStatus.SC_LOCKED);
1093: return;
1094: }
1095: }
1096:
1097: currentLock.tokens.addElement(lockToken);
1098: lock = currentLock;
1099: addLock = false;
1100:
1101: }
1102:
1103: }
1104:
1105: if (addLock) {
1106: lock.tokens.addElement(lockToken);
1107: collectionLocks.addElement(lock);
1108: }
1109:
1110: } else {
1111:
1112: // Locking a single resource
1113:
1114: // Retrieving an already existing lock on that resource
1115: LockInfo presentLock = (LockInfo) resourceLocks
1116: .get(lock.path);
1117: if (presentLock != null) {
1118:
1119: if ((presentLock.isExclusive())
1120: || (lock.isExclusive())) {
1121: // If either lock is exclusive, the lock can't be
1122: // granted
1123: resp
1124: .sendError(WebdavStatus.SC_PRECONDITION_FAILED);
1125: return;
1126: } else {
1127: presentLock.tokens.addElement(lockToken);
1128: lock = presentLock;
1129: }
1130:
1131: } else {
1132:
1133: lock.tokens.addElement(lockToken);
1134: resourceLocks.put(lock.path, lock);
1135:
1136: // Checking if a resource exists at this path
1137: exists = true;
1138: try {
1139: object = resources.lookup(path);
1140: } catch (NamingException e) {
1141: exists = false;
1142: }
1143: if (!exists) {
1144:
1145: // "Creating" a lock-null resource
1146: int slash = lock.path.lastIndexOf('/');
1147: String parentPath = lock.path.substring(0,
1148: slash);
1149:
1150: Vector<String> lockNulls = lockNullResources
1151: .get(parentPath);
1152: if (lockNulls == null) {
1153: lockNulls = new Vector<String>();
1154: lockNullResources
1155: .put(parentPath, lockNulls);
1156: }
1157:
1158: lockNulls.addElement(lock.path);
1159:
1160: }
1161: // Add the Lock-Token header as by RFC 2518 8.10.1
1162: // - only do this for newly created locks
1163: resp.addHeader("Lock-Token", "<opaquelocktoken:"
1164: + lockToken + ">");
1165: }
1166:
1167: }
1168:
1169: }
1170:
1171: if (lockRequestType == LOCK_REFRESH) {
1172:
1173: String ifHeader = req.getHeader("If");
1174: if (ifHeader == null)
1175: ifHeader = "";
1176:
1177: // Checking resource locks
1178:
1179: LockInfo toRenew = (LockInfo) resourceLocks.get(path);
1180: Enumeration tokenList = null;
1181: if (lock != null) {
1182:
1183: // At least one of the tokens of the locks must have been given
1184:
1185: tokenList = toRenew.tokens.elements();
1186: while (tokenList.hasMoreElements()) {
1187: String token = (String) tokenList.nextElement();
1188: if (ifHeader.indexOf(token) != -1) {
1189: toRenew.expiresAt = lock.expiresAt;
1190: lock = toRenew;
1191: }
1192: }
1193:
1194: }
1195:
1196: // Checking inheritable collection locks
1197:
1198: Enumeration collectionLocksList = collectionLocks
1199: .elements();
1200: while (collectionLocksList.hasMoreElements()) {
1201: toRenew = (LockInfo) collectionLocksList.nextElement();
1202: if (path.equals(toRenew.path)) {
1203:
1204: tokenList = toRenew.tokens.elements();
1205: while (tokenList.hasMoreElements()) {
1206: String token = (String) tokenList.nextElement();
1207: if (ifHeader.indexOf(token) != -1) {
1208: toRenew.expiresAt = lock.expiresAt;
1209: lock = toRenew;
1210: }
1211: }
1212:
1213: }
1214: }
1215:
1216: }
1217:
1218: // Set the status, then generate the XML response containing
1219: // the lock information
1220: XMLWriter generatedXML = new XMLWriter();
1221: generatedXML.writeXMLHeader();
1222: generatedXML.writeElement(null, "prop"
1223: + generateNamespaceDeclarations(), XMLWriter.OPENING);
1224:
1225: generatedXML.writeElement(null, "lockdiscovery",
1226: XMLWriter.OPENING);
1227:
1228: lock.toXML(generatedXML);
1229:
1230: generatedXML.writeElement(null, "lockdiscovery",
1231: XMLWriter.CLOSING);
1232:
1233: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1234:
1235: resp.setStatus(WebdavStatus.SC_OK);
1236: resp.setContentType("text/xml; charset=UTF-8");
1237: Writer writer = resp.getWriter();
1238: writer.write(generatedXML.toString());
1239: writer.close();
1240:
1241: }
1242:
1243: /**
1244: * UNLOCK Method.
1245: */
1246: protected void doUnlock(HttpServletRequest req,
1247: HttpServletResponse resp) throws ServletException,
1248: IOException {
1249:
1250: if (readOnly) {
1251: resp.sendError(WebdavStatus.SC_FORBIDDEN);
1252: return;
1253: }
1254:
1255: if (isLocked(req)) {
1256: resp.sendError(WebdavStatus.SC_LOCKED);
1257: return;
1258: }
1259:
1260: String path = getRelativePath(req);
1261:
1262: String lockTokenHeader = req.getHeader("Lock-Token");
1263: if (lockTokenHeader == null)
1264: lockTokenHeader = "";
1265:
1266: // Checking resource locks
1267:
1268: LockInfo lock = (LockInfo) resourceLocks.get(path);
1269: Enumeration tokenList = null;
1270: if (lock != null) {
1271:
1272: // At least one of the tokens of the locks must have been given
1273:
1274: tokenList = lock.tokens.elements();
1275: while (tokenList.hasMoreElements()) {
1276: String token = (String) tokenList.nextElement();
1277: if (lockTokenHeader.indexOf(token) != -1) {
1278: lock.tokens.removeElement(token);
1279: }
1280: }
1281:
1282: if (lock.tokens.isEmpty()) {
1283: resourceLocks.remove(path);
1284: // Removing any lock-null resource which would be present
1285: lockNullResources.remove(path);
1286: }
1287:
1288: }
1289:
1290: // Checking inheritable collection locks
1291:
1292: Enumeration collectionLocksList = collectionLocks.elements();
1293: while (collectionLocksList.hasMoreElements()) {
1294: lock = (LockInfo) collectionLocksList.nextElement();
1295: if (path.equals(lock.path)) {
1296:
1297: tokenList = lock.tokens.elements();
1298: while (tokenList.hasMoreElements()) {
1299: String token = (String) tokenList.nextElement();
1300: if (lockTokenHeader.indexOf(token) != -1) {
1301: lock.tokens.removeElement(token);
1302: break;
1303: }
1304: }
1305:
1306: if (lock.tokens.isEmpty()) {
1307: collectionLocks.removeElement(lock);
1308: // Removing any lock-null resource which would be present
1309: lockNullResources.remove(path);
1310: }
1311:
1312: }
1313: }
1314:
1315: resp.setStatus(WebdavStatus.SC_NO_CONTENT);
1316:
1317: }
1318:
1319: /**
1320: * Return a context-relative path, beginning with a "/", that represents
1321: * the canonical version of the specified path after ".." and "." elements
1322: * are resolved out. If the specified path attempts to go outside the
1323: * boundaries of the current context (i.e. too many ".." path elements
1324: * are present), return <code>null</code> instead.
1325: *
1326: * @param path Path to be normalized
1327: */
1328: protected String normalize(String path) {
1329:
1330: if (path == null)
1331: return null;
1332:
1333: // Create a place for the normalized path
1334: String normalized = path;
1335:
1336: if (normalized == null)
1337: return (null);
1338:
1339: if (normalized.equals("/."))
1340: return "/";
1341:
1342: // Normalize the slashes and add leading slash if necessary
1343: if (normalized.indexOf('\\') >= 0)
1344: normalized = normalized.replace('\\', '/');
1345: if (!normalized.startsWith("/"))
1346: normalized = "/" + normalized;
1347:
1348: // Resolve occurrences of "//" in the normalized path
1349: while (true) {
1350: int index = normalized.indexOf("//");
1351: if (index < 0)
1352: break;
1353: normalized = normalized.substring(0, index)
1354: + normalized.substring(index + 1);
1355: }
1356:
1357: // Resolve occurrences of "/./" in the normalized path
1358: while (true) {
1359: int index = normalized.indexOf("/./");
1360: if (index < 0)
1361: break;
1362: normalized = normalized.substring(0, index)
1363: + normalized.substring(index + 2);
1364: }
1365:
1366: // Resolve occurrences of "/../" in the normalized path
1367: while (true) {
1368: int index = normalized.indexOf("/../");
1369: if (index < 0)
1370: break;
1371: if (index == 0)
1372: return (null); // Trying to go outside our context
1373: int index2 = normalized.lastIndexOf('/', index - 1);
1374: normalized = normalized.substring(0, index2)
1375: + normalized.substring(index + 3);
1376: }
1377:
1378: // Return the normalized path that we have completed
1379: return (normalized);
1380:
1381: }
1382:
1383: // -------------------------------------------------------- Private Methods
1384:
1385: /**
1386: * Generate the namespace declarations.
1387: */
1388: private String generateNamespaceDeclarations() {
1389: return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
1390: }
1391:
1392: /**
1393: * Check to see if a resource is currently write locked. The method
1394: * will look at the "If" header to make sure the client
1395: * has give the appropriate lock tokens.
1396: *
1397: * @param req Servlet request
1398: * @return boolean true if the resource is locked (and no appropriate
1399: * lock token has been found for at least one of the non-shared locks which
1400: * are present on the resource).
1401: */
1402: private boolean isLocked(HttpServletRequest req) {
1403:
1404: String path = getRelativePath(req);
1405:
1406: String ifHeader = req.getHeader("If");
1407: if (ifHeader == null)
1408: ifHeader = "";
1409:
1410: String lockTokenHeader = req.getHeader("Lock-Token");
1411: if (lockTokenHeader == null)
1412: lockTokenHeader = "";
1413:
1414: return isLocked(path, ifHeader + lockTokenHeader);
1415:
1416: }
1417:
1418: /**
1419: * Check to see if a resource is currently write locked.
1420: *
1421: * @param path Path of the resource
1422: * @param ifHeader "If" HTTP header which was included in the request
1423: * @return boolean true if the resource is locked (and no appropriate
1424: * lock token has been found for at least one of the non-shared locks which
1425: * are present on the resource).
1426: */
1427: private boolean isLocked(String path, String ifHeader) {
1428:
1429: // Checking resource locks
1430:
1431: LockInfo lock = (LockInfo) resourceLocks.get(path);
1432: Enumeration tokenList = null;
1433: if ((lock != null) && (lock.hasExpired())) {
1434: resourceLocks.remove(path);
1435: } else if (lock != null) {
1436:
1437: // At least one of the tokens of the locks must have been given
1438:
1439: tokenList = lock.tokens.elements();
1440: boolean tokenMatch = false;
1441: while (tokenList.hasMoreElements()) {
1442: String token = (String) tokenList.nextElement();
1443: if (ifHeader.indexOf(token) != -1)
1444: tokenMatch = true;
1445: }
1446: if (!tokenMatch)
1447: return true;
1448:
1449: }
1450:
1451: // Checking inheritable collection locks
1452:
1453: Enumeration collectionLocksList = collectionLocks.elements();
1454: while (collectionLocksList.hasMoreElements()) {
1455: lock = (LockInfo) collectionLocksList.nextElement();
1456: if (lock.hasExpired()) {
1457: collectionLocks.removeElement(lock);
1458: } else if (path.startsWith(lock.path)) {
1459:
1460: tokenList = lock.tokens.elements();
1461: boolean tokenMatch = false;
1462: while (tokenList.hasMoreElements()) {
1463: String token = (String) tokenList.nextElement();
1464: if (ifHeader.indexOf(token) != -1)
1465: tokenMatch = true;
1466: }
1467: if (!tokenMatch)
1468: return true;
1469:
1470: }
1471: }
1472:
1473: return false;
1474:
1475: }
1476:
1477: /**
1478: * Copy a resource.
1479: *
1480: * @param req Servlet request
1481: * @param resp Servlet response
1482: * @return boolean true if the copy is successful
1483: */
1484: private boolean copyResource(HttpServletRequest req,
1485: HttpServletResponse resp) throws ServletException,
1486: IOException {
1487:
1488: // Parsing destination header
1489:
1490: String destinationPath = req.getHeader("Destination");
1491:
1492: if (destinationPath == null) {
1493: resp.sendError(WebdavStatus.SC_BAD_REQUEST);
1494: return false;
1495: }
1496:
1497: // Remove url encoding from destination
1498: destinationPath = RequestUtil
1499: .URLDecode(destinationPath, "UTF8");
1500:
1501: int protocolIndex = destinationPath.indexOf("://");
1502: if (protocolIndex >= 0) {
1503: // if the Destination URL contains the protocol, we can safely
1504: // trim everything upto the first "/" character after "://"
1505: int firstSeparator = destinationPath.indexOf("/",
1506: protocolIndex + 4);
1507: if (firstSeparator < 0) {
1508: destinationPath = "/";
1509: } else {
1510: destinationPath = destinationPath
1511: .substring(firstSeparator);
1512: }
1513: } else {
1514: String hostName = req.getServerName();
1515: if ((hostName != null)
1516: && (destinationPath.startsWith(hostName))) {
1517: destinationPath = destinationPath.substring(hostName
1518: .length());
1519: }
1520:
1521: int portIndex = destinationPath.indexOf(":");
1522: if (portIndex >= 0) {
1523: destinationPath = destinationPath.substring(portIndex);
1524: }
1525:
1526: if (destinationPath.startsWith(":")) {
1527: int firstSeparator = destinationPath.indexOf("/");
1528: if (firstSeparator < 0) {
1529: destinationPath = "/";
1530: } else {
1531: destinationPath = destinationPath
1532: .substring(firstSeparator);
1533: }
1534: }
1535: }
1536:
1537: // Normalise destination path (remove '.' and '..')
1538: destinationPath = normalize(destinationPath);
1539:
1540: String contextPath = req.getContextPath();
1541: if ((contextPath != null)
1542: && (destinationPath.startsWith(contextPath))) {
1543: destinationPath = destinationPath.substring(contextPath
1544: .length());
1545: }
1546:
1547: String pathInfo = req.getPathInfo();
1548: if (pathInfo != null) {
1549: String servletPath = req.getServletPath();
1550: if ((servletPath != null)
1551: && (destinationPath.startsWith(servletPath))) {
1552: destinationPath = destinationPath.substring(servletPath
1553: .length());
1554: }
1555: }
1556:
1557: if (debug > 0)
1558: log("Dest path :" + destinationPath);
1559:
1560: if ((destinationPath.toUpperCase().startsWith("/WEB-INF"))
1561: || (destinationPath.toUpperCase()
1562: .startsWith("/META-INF"))) {
1563: resp.sendError(WebdavStatus.SC_FORBIDDEN);
1564: return false;
1565: }
1566:
1567: String path = getRelativePath(req);
1568:
1569: if ((path.toUpperCase().startsWith("/WEB-INF"))
1570: || (path.toUpperCase().startsWith("/META-INF"))) {
1571: resp.sendError(WebdavStatus.SC_FORBIDDEN);
1572: return false;
1573: }
1574:
1575: if (destinationPath.equals(path)) {
1576: resp.sendError(WebdavStatus.SC_FORBIDDEN);
1577: return false;
1578: }
1579:
1580: // Parsing overwrite header
1581:
1582: boolean overwrite = true;
1583: String overwriteHeader = req.getHeader("Overwrite");
1584:
1585: if (overwriteHeader != null) {
1586: if (overwriteHeader.equalsIgnoreCase("T")) {
1587: overwrite = true;
1588: } else {
1589: overwrite = false;
1590: }
1591: }
1592:
1593: // Overwriting the destination
1594:
1595: boolean exists = true;
1596: try {
1597: resources.lookup(destinationPath);
1598: } catch (NamingException e) {
1599: exists = false;
1600: }
1601:
1602: if (overwrite) {
1603:
1604: // Delete destination resource, if it exists
1605: if (exists) {
1606: if (!deleteResource(destinationPath, req, resp, true)) {
1607: return false;
1608: }
1609: } else {
1610: resp.setStatus(WebdavStatus.SC_CREATED);
1611: }
1612:
1613: } else {
1614:
1615: // If the destination exists, then it's a conflict
1616: if (exists) {
1617: resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
1618: return false;
1619: }
1620:
1621: }
1622:
1623: // Copying source to destination
1624:
1625: Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
1626:
1627: boolean result = copyResource(resources, errorList, path,
1628: destinationPath);
1629:
1630: if ((!result) || (!errorList.isEmpty())) {
1631:
1632: sendReport(req, resp, errorList);
1633: return false;
1634:
1635: }
1636:
1637: // Removing any lock-null resource which would be present at
1638: // the destination path
1639: lockNullResources.remove(destinationPath);
1640:
1641: return true;
1642:
1643: }
1644:
1645: /**
1646: * Copy a collection.
1647: *
1648: * @param resources Resources implementation to be used
1649: * @param errorList Hashtable containing the list of errors which occurred
1650: * during the copy operation
1651: * @param source Path of the resource to be copied
1652: * @param dest Destination path
1653: */
1654: private boolean copyResource(DirContext resources,
1655: Hashtable<String, Integer> errorList, String source,
1656: String dest) {
1657:
1658: if (debug > 1)
1659: log("Copy: " + source + " To: " + dest);
1660:
1661: Object object = null;
1662: try {
1663: object = resources.lookup(source);
1664: } catch (NamingException e) {
1665: }
1666:
1667: if (object instanceof DirContext) {
1668:
1669: try {
1670: resources.createSubcontext(dest);
1671: } catch (NamingException e) {
1672: errorList.put(dest, new Integer(
1673: WebdavStatus.SC_CONFLICT));
1674: return false;
1675: }
1676:
1677: try {
1678: NamingEnumeration enumeration = resources.list(source);
1679: while (enumeration.hasMoreElements()) {
1680: NameClassPair ncPair = (NameClassPair) enumeration
1681: .nextElement();
1682: String childDest = dest;
1683: if (!childDest.equals("/"))
1684: childDest += "/";
1685: childDest += ncPair.getName();
1686: String childSrc = source;
1687: if (!childSrc.equals("/"))
1688: childSrc += "/";
1689: childSrc += ncPair.getName();
1690: copyResource(resources, errorList, childSrc,
1691: childDest);
1692: }
1693: } catch (NamingException e) {
1694: errorList.put(dest, new Integer(
1695: WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1696: return false;
1697: }
1698:
1699: } else {
1700:
1701: if (object instanceof Resource) {
1702: try {
1703: resources.bind(dest, object);
1704: } catch (NamingException e) {
1705: errorList.put(source, new Integer(
1706: WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1707: return false;
1708: }
1709: } else {
1710: errorList.put(source, new Integer(
1711: WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1712: return false;
1713: }
1714:
1715: }
1716:
1717: return true;
1718:
1719: }
1720:
1721: /**
1722: * Delete a resource.
1723: *
1724: * @param req Servlet request
1725: * @param resp Servlet response
1726: * @return boolean true if the copy is successful
1727: */
1728: private boolean deleteResource(HttpServletRequest req,
1729: HttpServletResponse resp) throws ServletException,
1730: IOException {
1731:
1732: String path = getRelativePath(req);
1733:
1734: return deleteResource(path, req, resp, true);
1735:
1736: }
1737:
1738: /**
1739: * Delete a resource.
1740: *
1741: * @param path Path of the resource which is to be deleted
1742: * @param req Servlet request
1743: * @param resp Servlet response
1744: * @param setStatus Should the response status be set on successful
1745: * completion
1746: */
1747: private boolean deleteResource(String path, HttpServletRequest req,
1748: HttpServletResponse resp, boolean setStatus)
1749: throws ServletException, IOException {
1750:
1751: if ((path.toUpperCase().startsWith("/WEB-INF"))
1752: || (path.toUpperCase().startsWith("/META-INF"))) {
1753: resp.sendError(WebdavStatus.SC_FORBIDDEN);
1754: return false;
1755: }
1756:
1757: String ifHeader = req.getHeader("If");
1758: if (ifHeader == null)
1759: ifHeader = "";
1760:
1761: String lockTokenHeader = req.getHeader("Lock-Token");
1762: if (lockTokenHeader == null)
1763: lockTokenHeader = "";
1764:
1765: if (isLocked(path, ifHeader + lockTokenHeader)) {
1766: resp.sendError(WebdavStatus.SC_LOCKED);
1767: return false;
1768: }
1769:
1770: boolean exists = true;
1771: Object object = null;
1772: try {
1773: object = resources.lookup(path);
1774: } catch (NamingException e) {
1775: exists = false;
1776: }
1777:
1778: if (!exists) {
1779: resp.sendError(WebdavStatus.SC_NOT_FOUND);
1780: return false;
1781: }
1782:
1783: boolean collection = (object instanceof DirContext);
1784:
1785: if (!collection) {
1786: try {
1787: resources.unbind(path);
1788: } catch (NamingException e) {
1789: resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
1790: return false;
1791: }
1792: } else {
1793:
1794: Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
1795:
1796: deleteCollection(req, resources, path, errorList);
1797: try {
1798: resources.unbind(path);
1799: } catch (NamingException e) {
1800: errorList.put(path, new Integer(
1801: WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1802: }
1803:
1804: if (!errorList.isEmpty()) {
1805:
1806: sendReport(req, resp, errorList);
1807: return false;
1808:
1809: }
1810:
1811: }
1812: if (setStatus) {
1813: resp.setStatus(WebdavStatus.SC_NO_CONTENT);
1814: }
1815: return true;
1816:
1817: }
1818:
1819: /**
1820: * Deletes a collection.
1821: *
1822: * @param resources Resources implementation associated with the context
1823: * @param path Path to the collection to be deleted
1824: * @param errorList Contains the list of the errors which occurred
1825: */
1826: private void deleteCollection(HttpServletRequest req,
1827: DirContext resources, String path,
1828: Hashtable<String, Integer> errorList) {
1829:
1830: if (debug > 1)
1831: log("Delete:" + path);
1832:
1833: if ((path.toUpperCase().startsWith("/WEB-INF"))
1834: || (path.toUpperCase().startsWith("/META-INF"))) {
1835: errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
1836: return;
1837: }
1838:
1839: String ifHeader = req.getHeader("If");
1840: if (ifHeader == null)
1841: ifHeader = "";
1842:
1843: String lockTokenHeader = req.getHeader("Lock-Token");
1844: if (lockTokenHeader == null)
1845: lockTokenHeader = "";
1846:
1847: Enumeration enumeration = null;
1848: try {
1849: enumeration = resources.list(path);
1850: } catch (NamingException e) {
1851: errorList.put(path, new Integer(
1852: WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1853: return;
1854: }
1855:
1856: while (enumeration.hasMoreElements()) {
1857: NameClassPair ncPair = (NameClassPair) enumeration
1858: .nextElement();
1859: String childName = path;
1860: if (!childName.equals("/"))
1861: childName += "/";
1862: childName += ncPair.getName();
1863:
1864: if (isLocked(childName, ifHeader + lockTokenHeader)) {
1865:
1866: errorList.put(childName, new Integer(
1867: WebdavStatus.SC_LOCKED));
1868:
1869: } else {
1870:
1871: try {
1872: Object object = resources.lookup(childName);
1873: if (object instanceof DirContext) {
1874: deleteCollection(req, resources, childName,
1875: errorList);
1876: }
1877:
1878: try {
1879: resources.unbind(childName);
1880: } catch (NamingException e) {
1881: if (!(object instanceof DirContext)) {
1882: // If it's not a collection, then it's an unknown
1883: // error
1884: errorList
1885: .put(
1886: childName,
1887: new Integer(
1888: WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1889: }
1890: }
1891: } catch (NamingException e) {
1892: errorList.put(childName, new Integer(
1893: WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1894: }
1895: }
1896:
1897: }
1898:
1899: }
1900:
1901: /**
1902: * Send a multistatus element containing a complete error report to the
1903: * client.
1904: *
1905: * @param req Servlet request
1906: * @param resp Servlet response
1907: * @param errorList List of error to be displayed
1908: */
1909: private void sendReport(HttpServletRequest req,
1910: HttpServletResponse resp, Hashtable errorList)
1911: throws ServletException, IOException {
1912:
1913: resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
1914:
1915: String absoluteUri = req.getRequestURI();
1916: String relativePath = getRelativePath(req);
1917:
1918: XMLWriter generatedXML = new XMLWriter();
1919: generatedXML.writeXMLHeader();
1920:
1921: generatedXML.writeElement(null, "multistatus"
1922: + generateNamespaceDeclarations(), XMLWriter.OPENING);
1923:
1924: Enumeration pathList = errorList.keys();
1925: while (pathList.hasMoreElements()) {
1926:
1927: String errorPath = (String) pathList.nextElement();
1928: int errorCode = ((Integer) errorList.get(errorPath))
1929: .intValue();
1930:
1931: generatedXML.writeElement(null, "response",
1932: XMLWriter.OPENING);
1933:
1934: generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1935: String toAppend = errorPath
1936: .substring(relativePath.length());
1937: if (!toAppend.startsWith("/"))
1938: toAppend = "/" + toAppend;
1939: generatedXML.writeText(absoluteUri + toAppend);
1940: generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1941: generatedXML
1942: .writeElement(null, "status", XMLWriter.OPENING);
1943: generatedXML.writeText("HTTP/1.1 " + errorCode + " "
1944: + WebdavStatus.getStatusText(errorCode));
1945: generatedXML
1946: .writeElement(null, "status", XMLWriter.CLOSING);
1947:
1948: generatedXML.writeElement(null, "response",
1949: XMLWriter.CLOSING);
1950:
1951: }
1952:
1953: generatedXML.writeElement(null, "multistatus",
1954: XMLWriter.CLOSING);
1955:
1956: Writer writer = resp.getWriter();
1957: writer.write(generatedXML.toString());
1958: writer.close();
1959:
1960: }
1961:
1962: /**
1963: * Propfind helper method.
1964: *
1965: * @param req The servlet request
1966: * @param resources Resources object associated with this context
1967: * @param generatedXML XML response to the Propfind request
1968: * @param path Path of the current resource
1969: * @param type Propfind type
1970: * @param propertiesVector If the propfind type is find properties by
1971: * name, then this Vector contains those properties
1972: */
1973: private void parseProperties(HttpServletRequest req,
1974: XMLWriter generatedXML, String path, int type,
1975: Vector<String> propertiesVector) {
1976:
1977: // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1978: // (the "toUpperCase()" avoids problems on Windows systems)
1979: if (path.toUpperCase().startsWith("/WEB-INF")
1980: || path.toUpperCase().startsWith("/META-INF"))
1981: return;
1982:
1983: CacheEntry cacheEntry = resources.lookupCache(path);
1984:
1985: generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1986: String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK
1987: + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1988:
1989: // Generating href element
1990: generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1991:
1992: String href = req.getContextPath() + req.getServletPath();
1993: if ((href.endsWith("/")) && (path.startsWith("/")))
1994: href += path.substring(1);
1995: else
1996: href += path;
1997: if ((cacheEntry.context != null) && (!href.endsWith("/")))
1998: href += "/";
1999:
2000: generatedXML.writeText(rewriteUrl(href));
2001:
2002: generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
2003:
2004: String resourceName = path;
2005: int lastSlash = path.lastIndexOf('/');
2006: if (lastSlash != -1)
2007: resourceName = resourceName.substring(lastSlash + 1);
2008:
2009: switch (type) {
2010:
2011: case FIND_ALL_PROP:
2012:
2013: generatedXML.writeElement(null, "propstat",
2014: XMLWriter.OPENING);
2015: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2016:
2017: generatedXML.writeProperty(null, "creationdate",
2018: getISOCreationDate(cacheEntry.attributes
2019: .getCreation()));
2020: generatedXML.writeElement(null, "displayname",
2021: XMLWriter.OPENING);
2022: generatedXML.writeData(resourceName);
2023: generatedXML.writeElement(null, "displayname",
2024: XMLWriter.CLOSING);
2025: if (cacheEntry.resource != null) {
2026: generatedXML.writeProperty(null, "getlastmodified",
2027: FastHttpDateFormat
2028: .formatDate(cacheEntry.attributes
2029: .getLastModified(), null));
2030: generatedXML.writeProperty(null, "getcontentlength",
2031: String.valueOf(cacheEntry.attributes
2032: .getContentLength()));
2033: String contentType = getServletContext().getMimeType(
2034: cacheEntry.name);
2035: if (contentType != null) {
2036: generatedXML.writeProperty(null, "getcontenttype",
2037: contentType);
2038: }
2039: generatedXML.writeProperty(null, "getetag",
2040: getETag(cacheEntry.attributes));
2041: generatedXML.writeElement(null, "resourcetype",
2042: XMLWriter.NO_CONTENT);
2043: } else {
2044: generatedXML.writeElement(null, "resourcetype",
2045: XMLWriter.OPENING);
2046: generatedXML.writeElement(null, "collection",
2047: XMLWriter.NO_CONTENT);
2048: generatedXML.writeElement(null, "resourcetype",
2049: XMLWriter.CLOSING);
2050: }
2051:
2052: generatedXML.writeProperty(null, "source", "");
2053:
2054: String supportedLocks = "<lockentry>"
2055: + "<lockscope><exclusive/></lockscope>"
2056: + "<locktype><write/></locktype>" + "</lockentry>"
2057: + "<lockentry>"
2058: + "<lockscope><shared/></lockscope>"
2059: + "<locktype><write/></locktype>" + "</lockentry>";
2060: generatedXML.writeElement(null, "supportedlock",
2061: XMLWriter.OPENING);
2062: generatedXML.writeText(supportedLocks);
2063: generatedXML.writeElement(null, "supportedlock",
2064: XMLWriter.CLOSING);
2065:
2066: generateLockDiscovery(path, generatedXML);
2067:
2068: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2069: generatedXML
2070: .writeElement(null, "status", XMLWriter.OPENING);
2071: generatedXML.writeText(status);
2072: generatedXML
2073: .writeElement(null, "status", XMLWriter.CLOSING);
2074: generatedXML.writeElement(null, "propstat",
2075: XMLWriter.CLOSING);
2076:
2077: break;
2078:
2079: case FIND_PROPERTY_NAMES:
2080:
2081: generatedXML.writeElement(null, "propstat",
2082: XMLWriter.OPENING);
2083: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2084:
2085: generatedXML.writeElement(null, "creationdate",
2086: XMLWriter.NO_CONTENT);
2087: generatedXML.writeElement(null, "displayname",
2088: XMLWriter.NO_CONTENT);
2089: if (cacheEntry.resource != null) {
2090: generatedXML.writeElement(null, "getcontentlanguage",
2091: XMLWriter.NO_CONTENT);
2092: generatedXML.writeElement(null, "getcontentlength",
2093: XMLWriter.NO_CONTENT);
2094: generatedXML.writeElement(null, "getcontenttype",
2095: XMLWriter.NO_CONTENT);
2096: generatedXML.writeElement(null, "getetag",
2097: XMLWriter.NO_CONTENT);
2098: generatedXML.writeElement(null, "getlastmodified",
2099: XMLWriter.NO_CONTENT);
2100: }
2101: generatedXML.writeElement(null, "resourcetype",
2102: XMLWriter.NO_CONTENT);
2103: generatedXML.writeElement(null, "source",
2104: XMLWriter.NO_CONTENT);
2105: generatedXML.writeElement(null, "lockdiscovery",
2106: XMLWriter.NO_CONTENT);
2107:
2108: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2109: generatedXML
2110: .writeElement(null, "status", XMLWriter.OPENING);
2111: generatedXML.writeText(status);
2112: generatedXML
2113: .writeElement(null, "status", XMLWriter.CLOSING);
2114: generatedXML.writeElement(null, "propstat",
2115: XMLWriter.CLOSING);
2116:
2117: break;
2118:
2119: case FIND_BY_PROPERTY:
2120:
2121: Vector<String> propertiesNotFound = new Vector<String>();
2122:
2123: // Parse the list of properties
2124:
2125: generatedXML.writeElement(null, "propstat",
2126: XMLWriter.OPENING);
2127: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2128:
2129: Enumeration<String> properties = propertiesVector
2130: .elements();
2131:
2132: while (properties.hasMoreElements()) {
2133:
2134: String property = (String) properties.nextElement();
2135:
2136: if (property.equals("creationdate")) {
2137: generatedXML.writeProperty(null, "creationdate",
2138: getISOCreationDate(cacheEntry.attributes
2139: .getCreation()));
2140: } else if (property.equals("displayname")) {
2141: generatedXML.writeElement(null, "displayname",
2142: XMLWriter.OPENING);
2143: generatedXML.writeData(resourceName);
2144: generatedXML.writeElement(null, "displayname",
2145: XMLWriter.CLOSING);
2146: } else if (property.equals("getcontentlanguage")) {
2147: if (cacheEntry.context != null) {
2148: propertiesNotFound.addElement(property);
2149: } else {
2150: generatedXML.writeElement(null,
2151: "getcontentlanguage",
2152: XMLWriter.NO_CONTENT);
2153: }
2154: } else if (property.equals("getcontentlength")) {
2155: if (cacheEntry.context != null) {
2156: propertiesNotFound.addElement(property);
2157: } else {
2158: generatedXML.writeProperty(null,
2159: "getcontentlength", (String
2160: .valueOf(cacheEntry.attributes
2161: .getContentLength())));
2162: }
2163: } else if (property.equals("getcontenttype")) {
2164: if (cacheEntry.context != null) {
2165: propertiesNotFound.addElement(property);
2166: } else {
2167: generatedXML.writeProperty(null,
2168: "getcontenttype", getServletContext()
2169: .getMimeType(cacheEntry.name));
2170: }
2171: } else if (property.equals("getetag")) {
2172: if (cacheEntry.context != null) {
2173: propertiesNotFound.addElement(property);
2174: } else {
2175: generatedXML.writeProperty(null, "getetag",
2176: getETag(cacheEntry.attributes));
2177: }
2178: } else if (property.equals("getlastmodified")) {
2179: if (cacheEntry.context != null) {
2180: propertiesNotFound.addElement(property);
2181: } else {
2182: generatedXML.writeProperty(null,
2183: "getlastmodified",
2184: FastHttpDateFormat.formatDate(
2185: cacheEntry.attributes
2186: .getLastModified(),
2187: null));
2188: }
2189: } else if (property.equals("resourcetype")) {
2190: if (cacheEntry.context != null) {
2191: generatedXML.writeElement(null, "resourcetype",
2192: XMLWriter.OPENING);
2193: generatedXML.writeElement(null, "collection",
2194: XMLWriter.NO_CONTENT);
2195: generatedXML.writeElement(null, "resourcetype",
2196: XMLWriter.CLOSING);
2197: } else {
2198: generatedXML.writeElement(null, "resourcetype",
2199: XMLWriter.NO_CONTENT);
2200: }
2201: } else if (property.equals("source")) {
2202: generatedXML.writeProperty(null, "source", "");
2203: } else if (property.equals("supportedlock")) {
2204: supportedLocks = "<lockentry>"
2205: + "<lockscope><exclusive/></lockscope>"
2206: + "<locktype><write/></locktype>"
2207: + "</lockentry>" + "<lockentry>"
2208: + "<lockscope><shared/></lockscope>"
2209: + "<locktype><write/></locktype>"
2210: + "</lockentry>";
2211: generatedXML.writeElement(null, "supportedlock",
2212: XMLWriter.OPENING);
2213: generatedXML.writeText(supportedLocks);
2214: generatedXML.writeElement(null, "supportedlock",
2215: XMLWriter.CLOSING);
2216: } else if (property.equals("lockdiscovery")) {
2217: if (!generateLockDiscovery(path, generatedXML))
2218: propertiesNotFound.addElement(property);
2219: } else {
2220: propertiesNotFound.addElement(property);
2221: }
2222:
2223: }
2224:
2225: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2226: generatedXML
2227: .writeElement(null, "status", XMLWriter.OPENING);
2228: generatedXML.writeText(status);
2229: generatedXML
2230: .writeElement(null, "status", XMLWriter.CLOSING);
2231: generatedXML.writeElement(null, "propstat",
2232: XMLWriter.CLOSING);
2233:
2234: Enumeration propertiesNotFoundList = propertiesNotFound
2235: .elements();
2236:
2237: if (propertiesNotFoundList.hasMoreElements()) {
2238:
2239: status = new String(
2240: "HTTP/1.1 "
2241: + WebdavStatus.SC_NOT_FOUND
2242: + " "
2243: + WebdavStatus
2244: .getStatusText(WebdavStatus.SC_NOT_FOUND));
2245:
2246: generatedXML.writeElement(null, "propstat",
2247: XMLWriter.OPENING);
2248: generatedXML.writeElement(null, "prop",
2249: XMLWriter.OPENING);
2250:
2251: while (propertiesNotFoundList.hasMoreElements()) {
2252: generatedXML.writeElement(null,
2253: (String) propertiesNotFoundList
2254: .nextElement(),
2255: XMLWriter.NO_CONTENT);
2256: }
2257:
2258: generatedXML.writeElement(null, "prop",
2259: XMLWriter.CLOSING);
2260: generatedXML.writeElement(null, "status",
2261: XMLWriter.OPENING);
2262: generatedXML.writeText(status);
2263: generatedXML.writeElement(null, "status",
2264: XMLWriter.CLOSING);
2265: generatedXML.writeElement(null, "propstat",
2266: XMLWriter.CLOSING);
2267:
2268: }
2269:
2270: break;
2271:
2272: }
2273:
2274: generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
2275:
2276: }
2277:
2278: /**
2279: * Propfind helper method. Dispays the properties of a lock-null resource.
2280: *
2281: * @param resources Resources object associated with this context
2282: * @param generatedXML XML response to the Propfind request
2283: * @param path Path of the current resource
2284: * @param type Propfind type
2285: * @param propertiesVector If the propfind type is find properties by
2286: * name, then this Vector contains those properties
2287: */
2288: private void parseLockNullProperties(HttpServletRequest req,
2289: XMLWriter generatedXML, String path, int type,
2290: Vector propertiesVector) {
2291:
2292: // Exclude any resource in the /WEB-INF and /META-INF subdirectories
2293: // (the "toUpperCase()" avoids problems on Windows systems)
2294: if (path.toUpperCase().startsWith("/WEB-INF")
2295: || path.toUpperCase().startsWith("/META-INF"))
2296: return;
2297:
2298: // Retrieving the lock associated with the lock-null resource
2299: LockInfo lock = (LockInfo) resourceLocks.get(path);
2300:
2301: if (lock == null)
2302: return;
2303:
2304: generatedXML.writeElement(null, "response", XMLWriter.OPENING);
2305: String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK
2306: + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
2307:
2308: // Generating href element
2309: generatedXML.writeElement(null, "href", XMLWriter.OPENING);
2310:
2311: String absoluteUri = req.getRequestURI();
2312: String relativePath = getRelativePath(req);
2313: String toAppend = path.substring(relativePath.length());
2314: if (!toAppend.startsWith("/"))
2315: toAppend = "/" + toAppend;
2316:
2317: generatedXML.writeText(rewriteUrl(normalize(absoluteUri
2318: + toAppend)));
2319:
2320: generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
2321:
2322: String resourceName = path;
2323: int lastSlash = path.lastIndexOf('/');
2324: if (lastSlash != -1)
2325: resourceName = resourceName.substring(lastSlash + 1);
2326:
2327: switch (type) {
2328:
2329: case FIND_ALL_PROP:
2330:
2331: generatedXML.writeElement(null, "propstat",
2332: XMLWriter.OPENING);
2333: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2334:
2335: generatedXML.writeProperty(null, "creationdate",
2336: getISOCreationDate(lock.creationDate.getTime()));
2337: generatedXML.writeElement(null, "displayname",
2338: XMLWriter.OPENING);
2339: generatedXML.writeData(resourceName);
2340: generatedXML.writeElement(null, "displayname",
2341: XMLWriter.CLOSING);
2342: generatedXML.writeProperty(null, "getlastmodified",
2343: FastHttpDateFormat.formatDate(lock.creationDate
2344: .getTime(), null));
2345: generatedXML.writeProperty(null, "getcontentlength", String
2346: .valueOf(0));
2347: generatedXML.writeProperty(null, "getcontenttype", "");
2348: generatedXML.writeProperty(null, "getetag", "");
2349: generatedXML.writeElement(null, "resourcetype",
2350: XMLWriter.OPENING);
2351: generatedXML.writeElement(null, "lock-null",
2352: XMLWriter.NO_CONTENT);
2353: generatedXML.writeElement(null, "resourcetype",
2354: XMLWriter.CLOSING);
2355:
2356: generatedXML.writeProperty(null, "source", "");
2357:
2358: String supportedLocks = "<lockentry>"
2359: + "<lockscope><exclusive/></lockscope>"
2360: + "<locktype><write/></locktype>" + "</lockentry>"
2361: + "<lockentry>"
2362: + "<lockscope><shared/></lockscope>"
2363: + "<locktype><write/></locktype>" + "</lockentry>";
2364: generatedXML.writeElement(null, "supportedlock",
2365: XMLWriter.OPENING);
2366: generatedXML.writeText(supportedLocks);
2367: generatedXML.writeElement(null, "supportedlock",
2368: XMLWriter.CLOSING);
2369:
2370: generateLockDiscovery(path, generatedXML);
2371:
2372: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2373: generatedXML
2374: .writeElement(null, "status", XMLWriter.OPENING);
2375: generatedXML.writeText(status);
2376: generatedXML
2377: .writeElement(null, "status", XMLWriter.CLOSING);
2378: generatedXML.writeElement(null, "propstat",
2379: XMLWriter.CLOSING);
2380:
2381: break;
2382:
2383: case FIND_PROPERTY_NAMES:
2384:
2385: generatedXML.writeElement(null, "propstat",
2386: XMLWriter.OPENING);
2387: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2388:
2389: generatedXML.writeElement(null, "creationdate",
2390: XMLWriter.NO_CONTENT);
2391: generatedXML.writeElement(null, "displayname",
2392: XMLWriter.NO_CONTENT);
2393: generatedXML.writeElement(null, "getcontentlanguage",
2394: XMLWriter.NO_CONTENT);
2395: generatedXML.writeElement(null, "getcontentlength",
2396: XMLWriter.NO_CONTENT);
2397: generatedXML.writeElement(null, "getcontenttype",
2398: XMLWriter.NO_CONTENT);
2399: generatedXML.writeElement(null, "getetag",
2400: XMLWriter.NO_CONTENT);
2401: generatedXML.writeElement(null, "getlastmodified",
2402: XMLWriter.NO_CONTENT);
2403: generatedXML.writeElement(null, "resourcetype",
2404: XMLWriter.NO_CONTENT);
2405: generatedXML.writeElement(null, "source",
2406: XMLWriter.NO_CONTENT);
2407: generatedXML.writeElement(null, "lockdiscovery",
2408: XMLWriter.NO_CONTENT);
2409:
2410: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2411: generatedXML
2412: .writeElement(null, "status", XMLWriter.OPENING);
2413: generatedXML.writeText(status);
2414: generatedXML
2415: .writeElement(null, "status", XMLWriter.CLOSING);
2416: generatedXML.writeElement(null, "propstat",
2417: XMLWriter.CLOSING);
2418:
2419: break;
2420:
2421: case FIND_BY_PROPERTY:
2422:
2423: Vector<String> propertiesNotFound = new Vector<String>();
2424:
2425: // Parse the list of properties
2426:
2427: generatedXML.writeElement(null, "propstat",
2428: XMLWriter.OPENING);
2429: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2430:
2431: Enumeration properties = propertiesVector.elements();
2432:
2433: while (properties.hasMoreElements()) {
2434:
2435: String property = (String) properties.nextElement();
2436:
2437: if (property.equals("creationdate")) {
2438: generatedXML.writeProperty(null, "creationdate",
2439: getISOCreationDate(lock.creationDate
2440: .getTime()));
2441: } else if (property.equals("displayname")) {
2442: generatedXML.writeElement(null, "displayname",
2443: XMLWriter.OPENING);
2444: generatedXML.writeData(resourceName);
2445: generatedXML.writeElement(null, "displayname",
2446: XMLWriter.CLOSING);
2447: } else if (property.equals("getcontentlanguage")) {
2448: generatedXML.writeElement(null,
2449: "getcontentlanguage", XMLWriter.NO_CONTENT);
2450: } else if (property.equals("getcontentlength")) {
2451: generatedXML.writeProperty(null,
2452: "getcontentlength", (String.valueOf(0)));
2453: } else if (property.equals("getcontenttype")) {
2454: generatedXML.writeProperty(null, "getcontenttype",
2455: "");
2456: } else if (property.equals("getetag")) {
2457: generatedXML.writeProperty(null, "getetag", "");
2458: } else if (property.equals("getlastmodified")) {
2459: generatedXML.writeProperty(null, "getlastmodified",
2460: FastHttpDateFormat.formatDate(
2461: lock.creationDate.getTime(), null));
2462: } else if (property.equals("resourcetype")) {
2463: generatedXML.writeElement(null, "resourcetype",
2464: XMLWriter.OPENING);
2465: generatedXML.writeElement(null, "lock-null",
2466: XMLWriter.NO_CONTENT);
2467: generatedXML.writeElement(null, "resourcetype",
2468: XMLWriter.CLOSING);
2469: } else if (property.equals("source")) {
2470: generatedXML.writeProperty(null, "source", "");
2471: } else if (property.equals("supportedlock")) {
2472: supportedLocks = "<lockentry>"
2473: + "<lockscope><exclusive/></lockscope>"
2474: + "<locktype><write/></locktype>"
2475: + "</lockentry>" + "<lockentry>"
2476: + "<lockscope><shared/></lockscope>"
2477: + "<locktype><write/></locktype>"
2478: + "</lockentry>";
2479: generatedXML.writeElement(null, "supportedlock",
2480: XMLWriter.OPENING);
2481: generatedXML.writeText(supportedLocks);
2482: generatedXML.writeElement(null, "supportedlock",
2483: XMLWriter.CLOSING);
2484: } else if (property.equals("lockdiscovery")) {
2485: if (!generateLockDiscovery(path, generatedXML))
2486: propertiesNotFound.addElement(property);
2487: } else {
2488: propertiesNotFound.addElement(property);
2489: }
2490:
2491: }
2492:
2493: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2494: generatedXML
2495: .writeElement(null, "status", XMLWriter.OPENING);
2496: generatedXML.writeText(status);
2497: generatedXML
2498: .writeElement(null, "status", XMLWriter.CLOSING);
2499: generatedXML.writeElement(null, "propstat",
2500: XMLWriter.CLOSING);
2501:
2502: Enumeration propertiesNotFoundList = propertiesNotFound
2503: .elements();
2504:
2505: if (propertiesNotFoundList.hasMoreElements()) {
2506:
2507: status = new String(
2508: "HTTP/1.1 "
2509: + WebdavStatus.SC_NOT_FOUND
2510: + " "
2511: + WebdavStatus
2512: .getStatusText(WebdavStatus.SC_NOT_FOUND));
2513:
2514: generatedXML.writeElement(null, "propstat",
2515: XMLWriter.OPENING);
2516: generatedXML.writeElement(null, "prop",
2517: XMLWriter.OPENING);
2518:
2519: while (propertiesNotFoundList.hasMoreElements()) {
2520: generatedXML.writeElement(null,
2521: (String) propertiesNotFoundList
2522: .nextElement(),
2523: XMLWriter.NO_CONTENT);
2524: }
2525:
2526: generatedXML.writeElement(null, "prop",
2527: XMLWriter.CLOSING);
2528: generatedXML.writeElement(null, "status",
2529: XMLWriter.OPENING);
2530: generatedXML.writeText(status);
2531: generatedXML.writeElement(null, "status",
2532: XMLWriter.CLOSING);
2533: generatedXML.writeElement(null, "propstat",
2534: XMLWriter.CLOSING);
2535:
2536: }
2537:
2538: break;
2539:
2540: }
2541:
2542: generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
2543:
2544: }
2545:
2546: /**
2547: * Print the lock discovery information associated with a path.
2548: *
2549: * @param path Path
2550: * @param generatedXML XML data to which the locks info will be appended
2551: * @return true if at least one lock was displayed
2552: */
2553: private boolean generateLockDiscovery(String path,
2554: XMLWriter generatedXML) {
2555:
2556: LockInfo resourceLock = (LockInfo) resourceLocks.get(path);
2557: Enumeration collectionLocksList = collectionLocks.elements();
2558:
2559: boolean wroteStart = false;
2560:
2561: if (resourceLock != null) {
2562: wroteStart = true;
2563: generatedXML.writeElement(null, "lockdiscovery",
2564: XMLWriter.OPENING);
2565: resourceLock.toXML(generatedXML);
2566: }
2567:
2568: while (collectionLocksList.hasMoreElements()) {
2569: LockInfo currentLock = (LockInfo) collectionLocksList
2570: .nextElement();
2571: if (path.startsWith(currentLock.path)) {
2572: if (!wroteStart) {
2573: wroteStart = true;
2574: generatedXML.writeElement(null, "lockdiscovery",
2575: XMLWriter.OPENING);
2576: }
2577: currentLock.toXML(generatedXML);
2578: }
2579: }
2580:
2581: if (wroteStart) {
2582: generatedXML.writeElement(null, "lockdiscovery",
2583: XMLWriter.CLOSING);
2584: } else {
2585: return false;
2586: }
2587:
2588: return true;
2589:
2590: }
2591:
2592: /**
2593: * Get creation date in ISO format.
2594: */
2595: private String getISOCreationDate(long creationDate) {
2596: StringBuffer creationDateValue = new StringBuffer(
2597: creationDateFormat.format(new Date(creationDate)));
2598: /*
2599: int offset = Calendar.getInstance().getTimeZone().getRawOffset()
2600: / 3600000; // FIXME ?
2601: if (offset < 0) {
2602: creationDateValue.append("-");
2603: offset = -offset;
2604: } else if (offset > 0) {
2605: creationDateValue.append("+");
2606: }
2607: if (offset != 0) {
2608: if (offset < 10)
2609: creationDateValue.append("0");
2610: creationDateValue.append(offset + ":00");
2611: } else {
2612: creationDateValue.append("Z");
2613: }
2614: */
2615: return creationDateValue.toString();
2616: }
2617:
2618: /**
2619: * Determines the methods normally allowed for the resource.
2620: *
2621: */
2622: private StringBuffer determineMethodsAllowed(DirContext resources,
2623: HttpServletRequest req) {
2624:
2625: StringBuffer methodsAllowed = new StringBuffer();
2626: boolean exists = true;
2627: Object object = null;
2628: try {
2629: String path = getRelativePath(req);
2630:
2631: object = resources.lookup(path);
2632: } catch (NamingException e) {
2633: exists = false;
2634: }
2635:
2636: if (!exists) {
2637: methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
2638: return methodsAllowed;
2639: }
2640:
2641: methodsAllowed
2642: .append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
2643: methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
2644:
2645: if (listings) {
2646: methodsAllowed.append(", PROPFIND");
2647: }
2648:
2649: if (!(object instanceof DirContext)) {
2650: methodsAllowed.append(", PUT");
2651: }
2652:
2653: return methodsAllowed;
2654: }
2655:
2656: // -------------------------------------------------- LockInfo Inner Class
2657:
2658: /**
2659: * Holds a lock information.
2660: */
2661: private class LockInfo {
2662:
2663: // -------------------------------------------------------- Constructor
2664:
2665: /**
2666: * Constructor.
2667: */
2668: public LockInfo() {
2669:
2670: }
2671:
2672: // ------------------------------------------------- Instance Variables
2673:
2674: String path = "/";
2675: String type = "write";
2676: String scope = "exclusive";
2677: int depth = 0;
2678: String owner = "";
2679: Vector<String> tokens = new Vector<String>();
2680: long expiresAt = 0;
2681: Date creationDate = new Date();
2682:
2683: // ----------------------------------------------------- Public Methods
2684:
2685: /**
2686: * Get a String representation of this lock token.
2687: */
2688: public String toString() {
2689:
2690: String result = "Type:" + type + "\n";
2691: result += "Scope:" + scope + "\n";
2692: result += "Depth:" + depth + "\n";
2693: result += "Owner:" + owner + "\n";
2694: result += "Expiration:"
2695: + FastHttpDateFormat.formatDate(expiresAt, null)
2696: + "\n";
2697: Enumeration tokensList = tokens.elements();
2698: while (tokensList.hasMoreElements()) {
2699: result += "Token:" + tokensList.nextElement() + "\n";
2700: }
2701: return result;
2702:
2703: }
2704:
2705: /**
2706: * Return true if the lock has expired.
2707: */
2708: public boolean hasExpired() {
2709: return (System.currentTimeMillis() > expiresAt);
2710: }
2711:
2712: /**
2713: * Return true if the lock is exclusive.
2714: */
2715: public boolean isExclusive() {
2716:
2717: return (scope.equals("exclusive"));
2718:
2719: }
2720:
2721: /**
2722: * Get an XML representation of this lock token. This method will
2723: * append an XML fragment to the given XML writer.
2724: */
2725: public void toXML(XMLWriter generatedXML) {
2726:
2727: generatedXML.writeElement(null, "activelock",
2728: XMLWriter.OPENING);
2729:
2730: generatedXML.writeElement(null, "locktype",
2731: XMLWriter.OPENING);
2732: generatedXML.writeElement(null, type, XMLWriter.NO_CONTENT);
2733: generatedXML.writeElement(null, "locktype",
2734: XMLWriter.CLOSING);
2735:
2736: generatedXML.writeElement(null, "lockscope",
2737: XMLWriter.OPENING);
2738: generatedXML
2739: .writeElement(null, scope, XMLWriter.NO_CONTENT);
2740: generatedXML.writeElement(null, "lockscope",
2741: XMLWriter.CLOSING);
2742:
2743: generatedXML.writeElement(null, "depth", XMLWriter.OPENING);
2744: if (depth == INFINITY) {
2745: generatedXML.writeText("Infinity");
2746: } else {
2747: generatedXML.writeText("0");
2748: }
2749: generatedXML.writeElement(null, "depth", XMLWriter.CLOSING);
2750:
2751: generatedXML.writeElement(null, "owner", XMLWriter.OPENING);
2752: generatedXML.writeText(owner);
2753: generatedXML.writeElement(null, "owner", XMLWriter.CLOSING);
2754:
2755: generatedXML.writeElement(null, "timeout",
2756: XMLWriter.OPENING);
2757: long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
2758: generatedXML.writeText("Second-" + timeout);
2759: generatedXML.writeElement(null, "timeout",
2760: XMLWriter.CLOSING);
2761:
2762: generatedXML.writeElement(null, "locktoken",
2763: XMLWriter.OPENING);
2764: Enumeration tokensList = tokens.elements();
2765: while (tokensList.hasMoreElements()) {
2766: generatedXML.writeElement(null, "href",
2767: XMLWriter.OPENING);
2768: generatedXML.writeText("opaquelocktoken:"
2769: + tokensList.nextElement());
2770: generatedXML.writeElement(null, "href",
2771: XMLWriter.CLOSING);
2772: }
2773: generatedXML.writeElement(null, "locktoken",
2774: XMLWriter.CLOSING);
2775:
2776: generatedXML.writeElement(null, "activelock",
2777: XMLWriter.CLOSING);
2778:
2779: }
2780:
2781: }
2782:
2783: };
2784:
2785: // -------------------------------------------------------- WebdavStatus Class
2786:
2787: /**
2788: * Wraps the HttpServletResponse class to abstract the
2789: * specific protocol used. To support other protocols
2790: * we would only need to modify this class and the
2791: * WebDavRetCode classes.
2792: *
2793: * @author Marc Eaddy
2794: * @version 1.0, 16 Nov 1997
2795: */
2796: class WebdavStatus {
2797:
2798: // ----------------------------------------------------- Instance Variables
2799:
2800: /**
2801: * This Hashtable contains the mapping of HTTP and WebDAV
2802: * status codes to descriptive text. This is a static
2803: * variable.
2804: */
2805: private static Hashtable<Integer, String> mapStatusCodes = new Hashtable<Integer, String>();
2806:
2807: // ------------------------------------------------------ HTTP Status Codes
2808:
2809: /**
2810: * Status code (200) indicating the request succeeded normally.
2811: */
2812: public static final int SC_OK = HttpServletResponse.SC_OK;
2813:
2814: /**
2815: * Status code (201) indicating the request succeeded and created
2816: * a new resource on the server.
2817: */
2818: public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
2819:
2820: /**
2821: * Status code (202) indicating that a request was accepted for
2822: * processing, but was not completed.
2823: */
2824: public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
2825:
2826: /**
2827: * Status code (204) indicating that the request succeeded but that
2828: * there was no new information to return.
2829: */
2830: public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
2831:
2832: /**
2833: * Status code (301) indicating that the resource has permanently
2834: * moved to a new location, and that future references should use a
2835: * new URI with their requests.
2836: */
2837: public static final int SC_MOVED_PERMANENTLY = HttpServletResponse.SC_MOVED_PERMANENTLY;
2838:
2839: /**
2840: * Status code (302) indicating that the resource has temporarily
2841: * moved to another location, but that future references should
2842: * still use the original URI to access the resource.
2843: */
2844: public static final int SC_MOVED_TEMPORARILY = HttpServletResponse.SC_MOVED_TEMPORARILY;
2845:
2846: /**
2847: * Status code (304) indicating that a conditional GET operation
2848: * found that the resource was available and not modified.
2849: */
2850: public static final int SC_NOT_MODIFIED = HttpServletResponse.SC_NOT_MODIFIED;
2851:
2852: /**
2853: * Status code (400) indicating the request sent by the client was
2854: * syntactically incorrect.
2855: */
2856: public static final int SC_BAD_REQUEST = HttpServletResponse.SC_BAD_REQUEST;
2857:
2858: /**
2859: * Status code (401) indicating that the request requires HTTP
2860: * authentication.
2861: */
2862: public static final int SC_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
2863:
2864: /**
2865: * Status code (403) indicating the server understood the request
2866: * but refused to fulfill it.
2867: */
2868: public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
2869:
2870: /**
2871: * Status code (404) indicating that the requested resource is not
2872: * available.
2873: */
2874: public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
2875:
2876: /**
2877: * Status code (500) indicating an error inside the HTTP service
2878: * which prevented it from fulfilling the request.
2879: */
2880: public static final int SC_INTERNAL_SERVER_ERROR = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
2881:
2882: /**
2883: * Status code (501) indicating the HTTP service does not support
2884: * the functionality needed to fulfill the request.
2885: */
2886: public static final int SC_NOT_IMPLEMENTED = HttpServletResponse.SC_NOT_IMPLEMENTED;
2887:
2888: /**
2889: * Status code (502) indicating that the HTTP server received an
2890: * invalid response from a server it consulted when acting as a
2891: * proxy or gateway.
2892: */
2893: public static final int SC_BAD_GATEWAY = HttpServletResponse.SC_BAD_GATEWAY;
2894:
2895: /**
2896: * Status code (503) indicating that the HTTP service is
2897: * temporarily overloaded, and unable to handle the request.
2898: */
2899: public static final int SC_SERVICE_UNAVAILABLE = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
2900:
2901: /**
2902: * Status code (100) indicating the client may continue with
2903: * its request. This interim response is used to inform the
2904: * client that the initial part of the request has been
2905: * received and has not yet been rejected by the server.
2906: */
2907: public static final int SC_CONTINUE = 100;
2908:
2909: /**
2910: * Status code (405) indicating the method specified is not
2911: * allowed for the resource.
2912: */
2913: public static final int SC_METHOD_NOT_ALLOWED = 405;
2914:
2915: /**
2916: * Status code (409) indicating that the request could not be
2917: * completed due to a conflict with the current state of the
2918: * resource.
2919: */
2920: public static final int SC_CONFLICT = 409;
2921:
2922: /**
2923: * Status code (412) indicating the precondition given in one
2924: * or more of the request-header fields evaluated to false
2925: * when it was tested on the server.
2926: */
2927: public static final int SC_PRECONDITION_FAILED = 412;
2928:
2929: /**
2930: * Status code (413) indicating the server is refusing to
2931: * process a request because the request entity is larger
2932: * than the server is willing or able to process.
2933: */
2934: public static final int SC_REQUEST_TOO_LONG = 413;
2935:
2936: /**
2937: * Status code (415) indicating the server is refusing to service
2938: * the request because the entity of the request is in a format
2939: * not supported by the requested resource for the requested
2940: * method.
2941: */
2942: public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
2943:
2944: // -------------------------------------------- Extended WebDav status code
2945:
2946: /**
2947: * Status code (207) indicating that the response requires
2948: * providing status for multiple independent operations.
2949: */
2950: public static final int SC_MULTI_STATUS = 207;
2951: // This one colides with HTTP 1.1
2952: // "207 Parital Update OK"
2953:
2954: /**
2955: * Status code (418) indicating the entity body submitted with
2956: * the PATCH method was not understood by the resource.
2957: */
2958: public static final int SC_UNPROCESSABLE_ENTITY = 418;
2959: // This one colides with HTTP 1.1
2960: // "418 Reauthentication Required"
2961:
2962: /**
2963: * Status code (419) indicating that the resource does not have
2964: * sufficient space to record the state of the resource after the
2965: * execution of this method.
2966: */
2967: public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
2968: // This one colides with HTTP 1.1
2969: // "419 Proxy Reauthentication Required"
2970:
2971: /**
2972: * Status code (420) indicating the method was not executed on
2973: * a particular resource within its scope because some part of
2974: * the method's execution failed causing the entire method to be
2975: * aborted.
2976: */
2977: public static final int SC_METHOD_FAILURE = 420;
2978:
2979: /**
2980: * Status code (423) indicating the destination resource of a
2981: * method is locked, and either the request did not contain a
2982: * valid Lock-Info header, or the Lock-Info header identifies
2983: * a lock held by another principal.
2984: */
2985: public static final int SC_LOCKED = 423;
2986:
2987: // ------------------------------------------------------------ Initializer
2988:
2989: static {
2990: // HTTP 1.0 tatus Code
2991: addStatusCodeMap(SC_OK, "OK");
2992: addStatusCodeMap(SC_CREATED, "Created");
2993: addStatusCodeMap(SC_ACCEPTED, "Accepted");
2994: addStatusCodeMap(SC_NO_CONTENT, "No Content");
2995: addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
2996: addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
2997: addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
2998: addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
2999: addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
3000: addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
3001: addStatusCodeMap(SC_NOT_FOUND, "Not Found");
3002: addStatusCodeMap(SC_INTERNAL_SERVER_ERROR,
3003: "Internal Server Error");
3004: addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
3005: addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
3006: addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
3007: addStatusCodeMap(SC_CONTINUE, "Continue");
3008: addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
3009: addStatusCodeMap(SC_CONFLICT, "Conflict");
3010: addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
3011: addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
3012: addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE,
3013: "Unsupported Media Type");
3014: // WebDav Status Codes
3015: addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
3016: addStatusCodeMap(SC_UNPROCESSABLE_ENTITY,
3017: "Unprocessable Entity");
3018: addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE,
3019: "Insufficient Space On Resource");
3020: addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
3021: addStatusCodeMap(SC_LOCKED, "Locked");
3022: }
3023:
3024: // --------------------------------------------------------- Public Methods
3025:
3026: /**
3027: * Returns the HTTP status text for the HTTP or WebDav status code
3028: * specified by looking it up in the static mapping. This is a
3029: * static function.
3030: *
3031: * @param nHttpStatusCode [IN] HTTP or WebDAV status code
3032: * @return A string with a short descriptive phrase for the
3033: * HTTP status code (e.g., "OK").
3034: */
3035: public static String getStatusText(int nHttpStatusCode) {
3036: Integer intKey = new Integer(nHttpStatusCode);
3037:
3038: if (!mapStatusCodes.containsKey(intKey)) {
3039: return "";
3040: } else {
3041: return (String) mapStatusCodes.get(intKey);
3042: }
3043: }
3044:
3045: // -------------------------------------------------------- Private Methods
3046:
3047: /**
3048: * Adds a new status code -> status text mapping. This is a static
3049: * method because the mapping is a static variable.
3050: *
3051: * @param nKey [IN] HTTP or WebDAV status code
3052: * @param strVal [IN] HTTP status text
3053: */
3054: private static void addStatusCodeMap(int nKey, String strVal) {
3055: mapStatusCodes.put(new Integer(nKey), strVal);
3056: }
3057:
3058: };
|