0001: /**********************************************************************************
0002: * $URL: https://source.sakaiproject.org/svn/dav/tags/sakai_2-4-1/dav/src/java/org/sakaiproject/dav/DavServlet.java $
0003: * $Id: DavServlet.java 22182 2007-03-05 18:40:15Z ajpoland@iupui.edu $
0004: ***********************************************************************************
0005: *
0006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
0007: *
0008: * Licensed under the Educational Community License, Version 1.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.opensource.org/licenses/ecl1.php
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: **********************************************************************************/package org.sakaiproject.dav;
0021:
0022: import java.io.ByteArrayInputStream;
0023: import java.io.ByteArrayOutputStream;
0024: import java.io.IOException;
0025: import java.io.InputStream;
0026: import java.io.OutputStream;
0027: import java.io.OutputStreamWriter;
0028: import java.io.PrintWriter;
0029: import java.io.StringWriter;
0030: import java.io.Writer;
0031: import java.security.MessageDigest;
0032: import java.security.NoSuchAlgorithmException;
0033: import java.security.Principal;
0034: import java.text.SimpleDateFormat;
0035: import java.util.BitSet;
0036: import java.util.Collections;
0037: import java.util.Date;
0038: import java.util.Enumeration;
0039: import java.util.Hashtable;
0040: import java.util.Iterator;
0041: import java.util.List;
0042: import java.util.Locale;
0043: import java.util.Properties;
0044: import java.util.Stack;
0045: import java.util.TimeZone;
0046: import java.util.Vector;
0047:
0048: import javax.naming.NameClassPair;
0049: import javax.naming.NamingException;
0050: import javax.naming.directory.DirContext;
0051: import javax.servlet.ServletException;
0052: import javax.servlet.http.HttpServlet;
0053: import javax.servlet.http.HttpServletRequest;
0054: import javax.servlet.http.HttpServletResponse;
0055: import javax.xml.parsers.DocumentBuilder;
0056: import javax.xml.parsers.DocumentBuilderFactory;
0057: import javax.xml.parsers.ParserConfigurationException;
0058:
0059: import org.apache.catalina.util.DOMWriter;
0060: import org.apache.catalina.util.MD5Encoder;
0061: import org.apache.catalina.util.RequestUtil;
0062: import org.apache.catalina.util.XMLWriter;
0063: import org.apache.commons.logging.Log;
0064: import org.apache.commons.logging.LogFactory;
0065: import org.sakaiproject.component.cover.ServerConfigurationService;
0066: import org.sakaiproject.content.api.ContentCollection;
0067: import org.sakaiproject.content.api.ContentCollectionEdit;
0068: import org.sakaiproject.content.api.ContentResource;
0069: import org.sakaiproject.content.api.ContentResourceEdit;
0070: import org.sakaiproject.content.cover.ContentHostingService;
0071: import org.sakaiproject.entity.api.Entity;
0072: import org.sakaiproject.entity.api.EntityPropertyNotDefinedException;
0073: import org.sakaiproject.entity.api.EntityPropertyTypeException;
0074: import org.sakaiproject.entity.api.ResourceProperties;
0075: import org.sakaiproject.entity.api.ResourcePropertiesEdit;
0076: import org.sakaiproject.event.api.NotificationService;
0077: import org.sakaiproject.event.cover.UsageSessionService;
0078: import org.sakaiproject.exception.IdInvalidException;
0079: import org.sakaiproject.exception.IdUnusedException;
0080: import org.sakaiproject.exception.IdLengthException;
0081: import org.sakaiproject.exception.IdUniquenessException;
0082: import org.sakaiproject.exception.IdUsedException;
0083: import org.sakaiproject.exception.InUseException;
0084: import org.sakaiproject.exception.InconsistentException;
0085: import org.sakaiproject.exception.OverQuotaException;
0086: import org.sakaiproject.exception.PermissionException;
0087: import org.sakaiproject.exception.ServerOverloadException;
0088: import org.sakaiproject.exception.TypeException;
0089: import org.sakaiproject.time.api.Time;
0090: import org.sakaiproject.time.api.TimeBreakdown;
0091: import org.sakaiproject.time.cover.TimeService;
0092: import org.sakaiproject.user.api.Authentication;
0093: import org.sakaiproject.user.api.AuthenticationException;
0094: import org.sakaiproject.user.api.Evidence;
0095: import org.sakaiproject.user.api.User;
0096: import org.sakaiproject.user.api.UserNotDefinedException;
0097: import org.sakaiproject.user.cover.AuthenticationManager;
0098: import org.sakaiproject.user.cover.UserDirectoryService;
0099: import org.sakaiproject.util.IdPwEvidence;
0100: import org.sakaiproject.util.StringUtil;
0101: import org.sakaiproject.util.Validator;
0102: import org.w3c.dom.Document;
0103: import org.w3c.dom.Element;
0104: import org.w3c.dom.Node;
0105: import org.w3c.dom.NodeList;
0106: import org.xml.sax.InputSource;
0107:
0108: /**
0109: * Servlet which adds support for WebDAV level 2. All the basic HTTP requests are handled by the DefaultServlet.
0110: */
0111: public class DavServlet extends HttpServlet {
0112: /** Our logger. */
0113: private static Log M_log = LogFactory.getLog(DavServlet.class);
0114:
0115: // -------------------------------------------------------------- Constants
0116:
0117: private static final String METHOD_HEAD = "HEAD";
0118:
0119: private static final String METHOD_PROPFIND = "PROPFIND";
0120:
0121: private static final String METHOD_PROPPATCH = "PROPPATCH";
0122:
0123: private static final String METHOD_OPTIONS = "OPTIONS";
0124:
0125: private static final String METHOD_MKCOL = "MKCOL";
0126:
0127: private static final String METHOD_COPY = "COPY";
0128:
0129: private static final String METHOD_MOVE = "MOVE";
0130:
0131: private static final String METHOD_LOCK = "LOCK";
0132:
0133: private static final String METHOD_UNLOCK = "UNLOCK";
0134:
0135: private static final String METHOD_GET = "GET";
0136:
0137: private static final String METHOD_PUT = "PUT";
0138:
0139: private static final String METHOD_POST = "POST";
0140:
0141: private static final String METHOD_DELETE = "DELETE";
0142:
0143: /**
0144: * Default depth is infite.
0145: */
0146: private static final int INFINITY = 3; // To limit tree browsing a bit
0147:
0148: /**
0149: * The debugging detail level for this servlet.
0150: */
0151: protected int debug = 0;
0152:
0153: /**
0154: * PROPFIND - Specify a property mask.
0155: */
0156: private static final int FIND_BY_PROPERTY = 0;
0157:
0158: /**
0159: * PROPFIND - Display all properties.
0160: */
0161: private static final int FIND_ALL_PROP = 1;
0162:
0163: /**
0164: * PROPFIND - Return property names.
0165: */
0166: private static final int FIND_PROPERTY_NAMES = 2;
0167:
0168: /**
0169: * Create a new lock.
0170: */
0171: private static final int LOCK_CREATION = 0;
0172:
0173: /**
0174: * Refresh lock.
0175: */
0176: private static final int LOCK_REFRESH = 1;
0177:
0178: /**
0179: * Default lock timeout value.
0180: */
0181: private static final int DEFAULT_TIMEOUT = 3600;
0182:
0183: /**
0184: * Maximum lock timeout.
0185: */
0186: private static final int MAX_TIMEOUT = 604800;
0187:
0188: /**
0189: * Read only flag. By default, it's set to true.
0190: */
0191: protected boolean readOnly = true;
0192:
0193: /**
0194: * Default namespace.
0195: */
0196: protected static final String DEFAULT_NAMESPACE = "DAV:";
0197:
0198: /** delimiter for form multiple values */
0199: static final String FORM_VALUE_DELIMETER = "^";
0200:
0201: /** used to id a log message */
0202: public static String ME = DavServlet.class.getName();
0203:
0204: // can be called on id with or withing adjustid, since
0205: // the prefixes we check for are not adjusted
0206: protected boolean prohibited(String id) {
0207: if (id == null)
0208: return false;
0209: if (id.startsWith("/attachment/")
0210: || id.equals("/attachment")
0211: || (doProtected
0212: && id.toLowerCase().indexOf("/protected") >= 0 && (!ContentHostingService
0213: .allowAddCollection(adjustId(id)))))
0214: return true;
0215: return false;
0216: }
0217:
0218: /**
0219: * Adjust the id (a resource id) to map between any tricks we want to play and the real id for content hosting.
0220: * @param id the id to adjust.
0221: * @return the adjusted id.
0222: */
0223: protected String adjustId(String id) {
0224: // Note: code stolen and to be kept synced wtih BaseContentService.parseEntityReference() -ggolden
0225:
0226: // map unknown prefix to, if "~", /user/, else /group/
0227: if (ContentHostingService.isShortRefs()) {
0228: // ignoring the first separator, get the first item separated from the rest
0229: String prefix[] = StringUtil.splitFirst(
0230: (id.length() > 1) ? id.substring(1) : "",
0231: Entity.SEPARATOR);
0232: if (prefix.length > 0) {
0233: // the following are recognized as full reference prefixe; if seen, the sort ref feature is not applied
0234: if (!(prefix[0].equals("group")
0235: || prefix[0].equals("user")
0236: || prefix[0].equals("group-user")
0237: || prefix[0].equals("public")
0238: || prefix[0].equals("private") || prefix[0]
0239: .equals("attachment"))) {
0240: String newPrefix = null;
0241:
0242: // a "~" starts a /user/ reference
0243: if (prefix[0].startsWith("~")) {
0244: newPrefix = Entity.SEPARATOR + "user"
0245: + Entity.SEPARATOR
0246: + prefix[0].substring(1);
0247: }
0248:
0249: // otherwise a /group/ reference
0250: else {
0251: newPrefix = Entity.SEPARATOR + "group"
0252: + Entity.SEPARATOR + prefix[0];
0253: }
0254:
0255: // reattach the tail (if any) to get the new id (if no taik, make sure we end with a separator if id started out with one)
0256: id = newPrefix
0257: + ((prefix.length > 1) ? (Entity.SEPARATOR + prefix[1])
0258: : (id.endsWith(Entity.SEPARATOR) ? Entity.SEPARATOR
0259: : ""));
0260: }
0261: }
0262: }
0263:
0264: // TODO: alias for site
0265:
0266: // recognize /user/EID and makeit /user/ID
0267: String parts[] = StringUtil.split(id, Entity.SEPARATOR);
0268: if (parts.length >= 3) {
0269: if (parts[1].equals("user")) {
0270: try {
0271: // if successful, the context is already a valid user id
0272: UserDirectoryService.getUser(parts[2]);
0273: } catch (UserNotDefinedException tryEid) {
0274: try {
0275: // try using it as an EID
0276: String userId = UserDirectoryService
0277: .getUserId(parts[2]);
0278:
0279: // switch to the ID
0280: parts[2] = userId;
0281: String newId = StringUtil.unsplit(parts,
0282: Entity.SEPARATOR);
0283:
0284: // add the trailing separator if needed
0285: if (id.endsWith(Entity.SEPARATOR))
0286: newId += Entity.SEPARATOR;
0287:
0288: id = newId;
0289: } catch (UserNotDefinedException notEid) {
0290: // if context was not a valid EID, leave it alone
0291: }
0292: }
0293: }
0294: }
0295: // recognize /group-user/SITE_ID/USER_EID and make it /group-user/SITE_ID/USER_ID
0296: if (parts.length >= 4) {
0297: if (parts[1].equals("group-user")) {
0298: try {
0299: // if successful, the context is already a valid user id
0300: UserDirectoryService.getUser(parts[3]);
0301: } catch (UserNotDefinedException tryEid) {
0302: try {
0303: // try using it as an EID
0304: String userId = UserDirectoryService
0305: .getUserId(parts[3]);
0306:
0307: // switch to the ID
0308: parts[3] = userId;
0309: String newId = StringUtil.unsplit(parts,
0310: Entity.SEPARATOR);
0311:
0312: // add the trailing separator if needed
0313: if (id.endsWith(Entity.SEPARATOR))
0314: newId += Entity.SEPARATOR;
0315:
0316: id = newId;
0317: } catch (UserNotDefinedException notEid) {
0318: // if context was not a valid EID, leave it alone
0319: }
0320: }
0321: }
0322: }
0323:
0324: return id;
0325: }
0326:
0327: /**
0328: * Simple date format for the creation date ISO representation (partial).
0329: */
0330: protected static final SimpleDateFormat creationDateFormat = new SimpleDateFormat(
0331: "yyyy-MM-dd'T'HH:mm:ss'Z'");
0332:
0333: static {
0334: creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
0335: }
0336:
0337: /**
0338: * Simple date format for the HTTP Date
0339: */
0340: protected static final SimpleDateFormat HttpDateFormat = new SimpleDateFormat(
0341: "EEE, d MMM yyyy hh:mm:ss z");
0342:
0343: static {
0344: HttpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
0345: };
0346:
0347: /**
0348: * The set of SimpleDateFormat formats to use in getDateHeader().
0349: */
0350: protected static final SimpleDateFormat formats[] = {
0351: new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",
0352: Locale.US),
0353: new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz",
0354: Locale.US),
0355: new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) };
0356:
0357: // ----------------------------------------------------- Instance Variables
0358:
0359: /**
0360: * Repository of the locks put on single resources.
0361: * <p>
0362: * Key : path <br>
0363: * Value : LockInfo
0364: */
0365: private Hashtable resourceLocks = new Hashtable();
0366:
0367: /**
0368: * Repository of the lock-null resources.
0369: * <p>
0370: * Key : path of the collection containing the lock-null resource<br>
0371: * Value : Vector of lock-null resource which are members of the collection. Each element of the Vector is the path associated with the lock-null resource.
0372: */
0373: private Hashtable lockNullResources = new Hashtable();
0374:
0375: /**
0376: * Vector of the heritable locks.
0377: * <p>
0378: * Key : path <br>
0379: * Value : LockInfo
0380: */
0381: private Vector collectionLocks = new Vector();
0382:
0383: /**
0384: * Secret information used to generate reasonably secure lock ids.
0385: */
0386: private String secret = "catalina";
0387:
0388: /**
0389: * Don't show directories starting with "protected" to non-owner This defaults off because it requires corresponding changes in AccessServlet, which currently aren't present.
0390: */
0391: private boolean doProtected = false;
0392:
0393: /**
0394: * The MD5 helper object for this class.
0395: */
0396: protected static final MD5Encoder md5Encoder = new MD5Encoder();
0397:
0398: /**
0399: * MD5 message digest provider.
0400: */
0401: protected static MessageDigest md5Helper;
0402:
0403: /**
0404: * Array of file patterns we are not supposed to accept on PUT
0405: */
0406: private String[] ignorePatterns = null;
0407:
0408: // --------------------------------------------------------- Public Methods
0409:
0410: /**
0411: * Initialize this servlet.
0412: */
0413: public void init() throws ServletException {
0414:
0415: // Set our properties from the initialization parameters
0416: String value = null;
0417: try {
0418: value = getServletConfig().getInitParameter("debug");
0419: debug = Integer.parseInt(value);
0420: } catch (Throwable t) {
0421: ;
0422: }
0423:
0424: try {
0425: value = getServletConfig().getInitParameter("readonly");
0426: if (value != null)
0427: readOnly = (new Boolean(value)).booleanValue();
0428: } catch (Throwable t) {
0429: ;
0430: }
0431:
0432: try {
0433: value = getServletConfig().getInitParameter("secret");
0434: if (value != null)
0435: secret = value;
0436: } catch (Throwable t) {
0437: ;
0438: }
0439:
0440: try {
0441: value = getServletConfig().getInitParameter("doprotected");
0442: if (value != null)
0443: doProtected = (new Boolean(value)).booleanValue();
0444: } catch (Throwable t) {
0445: ;
0446: }
0447:
0448: // load up the ignorePatterns from properties
0449: ignorePatterns = ServerConfigurationService
0450: .getStrings("webdav.ignore");
0451: if (ignorePatterns != null) {
0452: String outVal = "";
0453: for (int i = 0; i < ignorePatterns.length; i++) {
0454: if (outVal.length() > 0)
0455: outVal = outVal + " : ";
0456: outVal = outVal + ignorePatterns[i];
0457: }
0458: M_log.info("ignore patterns:" + outVal);
0459: }
0460:
0461: // Load the MD5 helper used to calculate signatures.
0462: try {
0463: md5Helper = MessageDigest.getInstance("MD5");
0464: } catch (NoSuchAlgorithmException e) {
0465: e.printStackTrace();
0466: throw new IllegalStateException();
0467: }
0468: }
0469:
0470: /** create the info */
0471: public SakaidavServletInfo newInfo(HttpServletRequest req) {
0472: return new SakaidavServletInfo(req);
0473:
0474: } // newInfo
0475:
0476: public class SakaidavServletInfo {
0477: // all properties from the request
0478: protected Properties m_options = null;
0479:
0480: public Properties getOptions() {
0481: return m_options;
0482: }
0483:
0484: /** construct from the req */
0485: public SakaidavServletInfo(HttpServletRequest req) {
0486: m_options = new Properties();
0487: String type = req.getContentType();
0488:
0489: Enumeration e = req.getParameterNames();
0490: while (e.hasMoreElements()) {
0491: String key = (String) e.nextElement();
0492: String[] values = req.getParameterValues(key);
0493: if (values.length == 1) {
0494: m_options.put(key, values[0]);
0495: } else {
0496: StringBuffer buf = new StringBuffer();
0497: for (int i = 0; i < values.length; i++) {
0498: buf.append(values[i] + FORM_VALUE_DELIMETER);
0499: }
0500: m_options.put(key, buf.toString());
0501: }
0502: }
0503:
0504: } // SakaidavServletInfo
0505:
0506: /** return the m_options as a string */
0507: public String optionsString() {
0508: StringBuffer buf = new StringBuffer(1024);
0509: Enumeration e = m_options.keys();
0510: while (e.hasMoreElements()) {
0511: String key = (String) e.nextElement();
0512: Object o = m_options.getProperty(key);
0513: if (o instanceof String) {
0514: buf.append(key);
0515: buf.append("=");
0516: buf.append(o.toString());
0517: buf.append("&");
0518: }
0519: }
0520:
0521: return buf.toString();
0522:
0523: } // optionsString
0524:
0525: } // SakaidavServletInfo
0526:
0527: // From DefaultServlet
0528:
0529: protected String getUserPropertyDisplayName(
0530: ResourceProperties props, String name) {
0531: String id = props.getProperty(name);
0532: if (id != null) {
0533: try {
0534: User u = UserDirectoryService.getUser(id);
0535: return u.getDisplayName();
0536: } catch (UserNotDefinedException e) {
0537: return id;
0538: }
0539: }
0540:
0541: return "unknown";
0542: }
0543:
0544: /**
0545: * Show HTTP header information.
0546: */
0547: protected void showRequestInfo(HttpServletRequest req) {
0548:
0549: if (M_log.isDebugEnabled())
0550: M_log.debug("DefaultServlet Request Info");
0551:
0552: // Show generic info
0553: if (M_log.isDebugEnabled())
0554: M_log.debug("Encoding : " + req.getCharacterEncoding());
0555: if (M_log.isDebugEnabled())
0556: M_log.debug("Length : " + req.getContentLength());
0557: if (M_log.isDebugEnabled())
0558: M_log.debug("Type : " + req.getContentType());
0559:
0560: if (M_log.isDebugEnabled())
0561: M_log.debug("Parameters");
0562:
0563: Enumeration parameters = req.getParameterNames();
0564:
0565: while (parameters.hasMoreElements()) {
0566: String paramName = (String) parameters.nextElement();
0567: String[] values = req.getParameterValues(paramName);
0568: System.out.print(paramName + " : ");
0569: for (int i = 0; i < values.length; i++) {
0570: System.out.print(values[i] + ", ");
0571: }
0572: }
0573:
0574: if (M_log.isDebugEnabled())
0575: M_log.debug("Protocol : " + req.getProtocol());
0576: if (M_log.isDebugEnabled())
0577: M_log.debug("Address : " + req.getRemoteAddr());
0578: if (M_log.isDebugEnabled())
0579: M_log.debug("Host : " + req.getRemoteHost());
0580: if (M_log.isDebugEnabled())
0581: M_log.debug("Scheme : " + req.getScheme());
0582: if (M_log.isDebugEnabled())
0583: M_log.debug("Server Name : " + req.getServerName());
0584: if (M_log.isDebugEnabled())
0585: M_log.debug("Server Port : " + req.getServerPort());
0586:
0587: if (M_log.isDebugEnabled())
0588: M_log.debug("Attributes");
0589:
0590: Enumeration attributes = req.getAttributeNames();
0591:
0592: while (attributes.hasMoreElements()) {
0593: String attributeName = (String) attributes.nextElement();
0594: System.out.print(attributeName + " : ");
0595: if (M_log.isDebugEnabled())
0596: M_log.debug(req.getAttribute(attributeName).toString());
0597: }
0598:
0599: // Show HTTP info
0600: if (M_log.isDebugEnabled())
0601: M_log.debug("HTTP Header Info");
0602:
0603: if (M_log.isDebugEnabled())
0604: M_log.debug("Authentication Type : " + req.getAuthType());
0605: if (M_log.isDebugEnabled())
0606: M_log.debug("HTTP Method : " + req.getMethod());
0607: if (M_log.isDebugEnabled())
0608: M_log.debug("Path Info : " + req.getPathInfo());
0609: if (M_log.isDebugEnabled())
0610: M_log.debug("Path translated : " + req.getPathTranslated());
0611: if (M_log.isDebugEnabled())
0612: M_log.debug("Query string : " + req.getQueryString());
0613: if (M_log.isDebugEnabled())
0614: M_log.debug("Remote user : " + req.getRemoteUser());
0615: if (M_log.isDebugEnabled())
0616: M_log.debug("Requested session id : "
0617: + req.getRequestedSessionId());
0618: if (M_log.isDebugEnabled())
0619: M_log.debug("Request URI : " + req.getRequestURI());
0620: if (M_log.isDebugEnabled())
0621: M_log.debug("Context path : " + req.getContextPath());
0622: if (M_log.isDebugEnabled())
0623: M_log.debug("Servlet path : " + req.getServletPath());
0624: if (M_log.isDebugEnabled())
0625: M_log.debug("User principal : " + req.getUserPrincipal());
0626: if (M_log.isDebugEnabled())
0627: M_log.debug("Headers : ");
0628:
0629: Enumeration headers = req.getHeaderNames();
0630:
0631: while (headers.hasMoreElements()) {
0632: String headerName = (String) headers.nextElement();
0633: System.out.print(headerName + " : ");
0634: if (M_log.isDebugEnabled())
0635: M_log.debug(req.getHeader(headerName));
0636: }
0637: }
0638:
0639: /**
0640: * Return the relative path associated with this servlet.
0641: *
0642: * @param request
0643: * The servlet request we are processing
0644: */
0645: protected String getRelativePath(HttpServletRequest request) {
0646:
0647: // Are we being processed by a RequestDispatcher.include()?
0648: if (request.getAttribute("javax.servlet.include.request_uri") != null) {
0649: String result = (String) request
0650: .getAttribute("javax.servlet.include.path_info");
0651: if (result == null)
0652: result = (String) request
0653: .getAttribute("javax.servlet.include.servlet_path");
0654: if ((result == null) || (result.equals("")))
0655: result = "/";
0656: return (result);
0657: }
0658:
0659: // Are we being processed by a RequestDispatcher.forward()?
0660: if (request.getAttribute("javax.servlet.forward.request_uri") != null) {
0661: String result = (String) request
0662: .getAttribute("javax.servlet.forward.path_info");
0663: if (result == null)
0664: result = (String) request
0665: .getAttribute("javax.servlet.forward.servlet_path");
0666: if ((result == null) || (result.equals("")))
0667: result = "/";
0668: return (result);
0669: }
0670:
0671: // No, extract the desired path directly from the request
0672: String result = request.getPathInfo();
0673: if (result == null) {
0674: result = request.getServletPath();
0675: }
0676: if ((result == null) || (result.equals(""))) {
0677: result = "/";
0678: }
0679: return normalize(result);
0680:
0681: }
0682:
0683: /**
0684: * Return a context-relative path, beginning with a "/", that represents the canonical version of the specified path after ".." and "." elements are resolved out. If the specified path attempts to go outside the boundaries of the current context (i.e.
0685: * too many ".." path elements are present), return <code>null</code> instead.
0686: *
0687: * @param path
0688: * Path to be normalized
0689: */
0690: protected String normalize(String path) {
0691:
0692: if (path == null)
0693: return null;
0694:
0695: // Create a place for the normalized path
0696: String normalized = path;
0697:
0698: /*
0699: * Commented out -- already URL-decoded in StandardContextMapper Decoding twice leaves the container vulnerable to %25 --> '%' attacks. if (normalized.indexOf('%') >= 0) normalized = RequestUtil.URLDecode(normalized, "UTF8");
0700: */
0701:
0702: if (normalized == null)
0703: return (null);
0704:
0705: if (normalized.equals("/."))
0706: return "/";
0707:
0708: // Normalize the slashes and add leading slash if necessary
0709: if (normalized.indexOf('\\') >= 0)
0710: normalized = normalized.replace('\\', '/');
0711: if (!normalized.startsWith("/"))
0712: normalized = "/" + normalized;
0713:
0714: // Resolve occurrences of "//" in the normalized path
0715: while (true) {
0716: int index = normalized.indexOf("//");
0717: if (index < 0)
0718: break;
0719: normalized = normalized.substring(0, index)
0720: + normalized.substring(index + 1);
0721: }
0722:
0723: // Resolve occurrences of "/./" in the normalized path
0724: while (true) {
0725: int index = normalized.indexOf("/./");
0726: if (index < 0)
0727: break;
0728: normalized = normalized.substring(0, index)
0729: + normalized.substring(index + 2);
0730: }
0731:
0732: // Resolve occurrences of "/../" in the normalized path
0733: while (true) {
0734: int index = normalized.indexOf("/../");
0735: if (index < 0)
0736: break;
0737: if (index == 0)
0738: return (null); // Trying to go outside our context
0739: int index2 = normalized.lastIndexOf('/', index - 1);
0740: normalized = normalized.substring(0, index2)
0741: + normalized.substring(index + 3);
0742: }
0743:
0744: // Return the normalized path that we have completed
0745: return (normalized);
0746:
0747: }
0748:
0749: /**
0750: * URL rewriter.
0751: *
0752: * @param path
0753: * Path which has to be rewiten
0754: */
0755: protected String rewriteUrl(String path) {
0756:
0757: /**
0758: * Note: This code portion is very similar to URLEncoder.encode. Unfortunately, there is no way to specify to the URLEncoder which characters should be encoded. Here, ' ' should be encoded as "%20" and '/' shouldn't be encoded.
0759: */
0760:
0761: int maxBytesPerChar = 10;
0762: int caseDiff = ('a' - 'A');
0763: StringBuffer rewrittenPath = new StringBuffer(path.length());
0764: ByteArrayOutputStream buf = new ByteArrayOutputStream(
0765: maxBytesPerChar);
0766: OutputStreamWriter writer = null;
0767: try {
0768: writer = new OutputStreamWriter(buf, "UTF8");
0769: } catch (Exception e) {
0770: e.printStackTrace();
0771: writer = new OutputStreamWriter(buf);
0772: }
0773:
0774: for (int i = 0; i < path.length(); i++) {
0775: int c = (int) path.charAt(i);
0776: if (safeCharacters.get(c)) {
0777: rewrittenPath.append((char) c);
0778: } else {
0779: // convert to external encoding before hex conversion
0780: try {
0781: writer.write(c);
0782: writer.flush();
0783: } catch (IOException e) {
0784: buf.reset();
0785: continue;
0786: }
0787: byte[] ba = buf.toByteArray();
0788: for (int j = 0; j < ba.length; j++) {
0789: // Converting each byte in the buffer
0790: byte toEncode = ba[j];
0791: rewrittenPath.append('%');
0792: int low = (int) (toEncode & 0x0f);
0793: int high = (int) ((toEncode & 0xf0) >> 4);
0794: rewrittenPath.append(hexadecimal[high]);
0795: rewrittenPath.append(hexadecimal[low]);
0796: }
0797: buf.reset();
0798: }
0799: }
0800:
0801: return rewrittenPath.toString();
0802:
0803: }
0804:
0805: /**
0806: * Array containing the safe characters set.
0807: */
0808: protected static BitSet safeCharacters;
0809:
0810: protected static final char[] hexadecimal = { '0', '1', '2', '3',
0811: '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
0812:
0813: // ----------------------------------------------------- Static Initializer
0814:
0815: static {
0816: safeCharacters = new BitSet(256);
0817: int i;
0818: for (i = 'a'; i <= 'z'; i++) {
0819: safeCharacters.set(i);
0820: }
0821: for (i = 'A'; i <= 'Z'; i++) {
0822: safeCharacters.set(i);
0823: }
0824: for (i = '0'; i <= '9'; i++) {
0825: safeCharacters.set(i);
0826: }
0827: safeCharacters.set('-');
0828: safeCharacters.set('_');
0829: safeCharacters.set('.');
0830: safeCharacters.set('*');
0831: safeCharacters.set('/');
0832: }
0833:
0834: // ------------------------------------------------------ Protected Methods
0835:
0836: /**
0837: * Return JAXP document builder instance.
0838: */
0839: protected DocumentBuilder getDocumentBuilder()
0840: throws ServletException {
0841: DocumentBuilder documentBuilder = null;
0842: try {
0843: documentBuilder = DocumentBuilderFactory.newInstance()
0844: .newDocumentBuilder();
0845: } catch (ParserConfigurationException e) {
0846: throw new ServletException("Sakaidavservlet.jaxpfailed");
0847: }
0848: return documentBuilder;
0849: }
0850:
0851: /**
0852: * Setup and cleanup around this request.
0853: *
0854: * @param req
0855: * HttpServletRequest object with the client request
0856: * @param res
0857: * HttpServletResponse object back to the client
0858: */
0859: protected void service(HttpServletRequest req,
0860: HttpServletResponse res) throws ServletException,
0861: java.io.IOException {
0862: SakaidavServletInfo info = newInfo(req);
0863:
0864: // try to authenticate based on a Principal (one of ours) in the req
0865: Principal prin = req.getUserPrincipal();
0866:
0867: if ((prin != null) && (prin instanceof DavPrincipal)) {
0868: String eid = prin.getName();
0869: String pw = ((DavPrincipal) prin).getPassword();
0870: Evidence e = new IdPwEvidence(eid, pw);
0871:
0872: // in older versions of this code, we didn't authenticate
0873: // if there was a session for this user. Unfortunately the
0874: // these are special non-sakai sessions, which do not
0875: // have real cookies attached. The cookie looks like
0876: // username-hostname. That means that they're easy to
0877: // fake. Since the DAV protocol doesn't actually
0878: // support sessions in the first place, most clients
0879: // won't use them. So it's a security hole without
0880: // any real benefit. Thus we check the password for
0881: // every transaction. The underlying sessions are still
0882: // a good idea, as they set the context for later
0883: // operations. But we can't depend upon the cookies for
0884: // authentication.
0885:
0886: // authenticate
0887: try {
0888: if ((eid.length() == 0) || (pw.length() == 0)) {
0889: throw new AuthenticationException(
0890: "missing required fields");
0891: }
0892:
0893: Authentication a = AuthenticationManager
0894: .authenticate(e);
0895:
0896: if (!UsageSessionService.login(a, req)) {
0897: // login failed
0898: res.sendError(401);
0899: return;
0900: }
0901: } catch (AuthenticationException ex) {
0902: // not authenticated
0903: res.sendError(401);
0904: return;
0905: }
0906: } else {
0907: // user name missing, so can't authenticate
0908: res.sendError(401);
0909: return;
0910: }
0911:
0912: // Setup... ?
0913:
0914: try {
0915: doDispatch(info, req, res);
0916: } finally {
0917: log(req, info);
0918: }
0919: }
0920:
0921: /** log a request processed */
0922: public void log(HttpServletRequest req, SakaidavServletInfo info) {
0923: M_log.debug("from:" + req.getRemoteAddr() + " path:"
0924: + req.getPathInfo() + " options: "
0925: + info.optionsString());
0926:
0927: } // log
0928:
0929: /**
0930: * Handles the special Webdav methods
0931: */
0932: protected void doDispatch(SakaidavServletInfo info,
0933: HttpServletRequest req, HttpServletResponse resp)
0934: throws ServletException, IOException {
0935:
0936: String method = req.getMethod();
0937:
0938: if (debug > 0) {
0939: String path = getRelativePath(req);
0940: if (M_log.isDebugEnabled())
0941: M_log.debug("SAKAIDAV doDispatch [" + method + "] "
0942: + path);
0943: }
0944:
0945: String remoteUser = req.getRemoteUser();
0946: if (M_log.isDebugEnabled())
0947: M_log.debug("SAKAIDAV remoteuser = " + remoteUser);
0948: if (remoteUser == null) {
0949: if (M_log.isDebugEnabled())
0950: M_log.debug("SAKAIDAV Requires Authorization");
0951: resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
0952: return;
0953: }
0954:
0955: if (method.equals(METHOD_PROPFIND)) {
0956: doPropfind(req, resp);
0957: } else if (method.equals(METHOD_PROPPATCH)) {
0958: doProppatch(req, resp);
0959: } else if (method.equals(METHOD_MKCOL)) {
0960: doMkcol(req, resp);
0961: } else if (method.equals(METHOD_COPY)) {
0962: doCopy(req, resp);
0963: } else if (method.equals(METHOD_MOVE)) {
0964: doMove(req, resp);
0965: } else if (method.equals(METHOD_LOCK)) {
0966: doLock(req, resp);
0967: } else if (method.equals(METHOD_UNLOCK)) {
0968: doUnlock(req, resp);
0969: } else if (method.equals(METHOD_GET)) {
0970: doGet(req, resp);
0971: } else if (method.equals(METHOD_PUT)) {
0972: doPut(req, resp);
0973: } else if (method.equals(METHOD_POST)) {
0974: doPost(req, resp);
0975: } else if (method.equals(METHOD_HEAD)) {
0976: doHead(req, resp);
0977: } else if (method.equals(METHOD_OPTIONS)) {
0978: doOptions(req, resp);
0979: } else if (method.equals(METHOD_DELETE)) {
0980: doDelete(req, resp);
0981: } else {
0982: M_log.warn("SAKAIDAV:Request not supported");
0983: resp.sendError(SakaidavStatus.SC_NOT_IMPLEMENTED);
0984: // showRequestInfo(req);
0985: }
0986:
0987: }
0988:
0989: /**
0990: * Determine if this path is one of the prefixes that we have been requested to ignore by the properties settings
0991: *
0992: * @param request
0993: * The servlet request we are processing
0994: */
0995: protected boolean isFileNameAllowed(HttpServletRequest req) {
0996: if (ignorePatterns == null)
0997: return true;
0998:
0999: String sakaiPath = getRelativePathSAKAI(req);
1000: for (int i = 0; i < ignorePatterns.length; i++) {
1001: if (sakaiPath.lastIndexOf(ignorePatterns[i]) > 0)
1002: return false;
1003: }
1004: return true;
1005: }
1006:
1007: /**
1008: * Process a HEAD request for the specified resource.
1009: *
1010: * @param request
1011: * The servlet request we are processing
1012: * @param response
1013: * The servlet response we are creating
1014: * @exception IOException
1015: * if an input/output error occurs
1016: * @exception ServletException
1017: * if a servlet-specified error occurs
1018: */
1019: protected void doHead(HttpServletRequest request,
1020: HttpServletResponse response) throws IOException,
1021: ServletException {
1022:
1023: // Call helper
1024: processHead(request, response);
1025: }
1026:
1027: /**
1028: * Helper to handle set Header information
1029: *
1030: * @param request
1031: * The servlet request we are processing
1032: * @param response
1033: * The servlet response we are creating
1034: * @exception IOException
1035: * if an input/output error occurs
1036: * @exception ServletException
1037: * if a servlet-specified error occurs
1038: * @return boolean false if there was an error, true if the head variable all were set
1039: */
1040: private boolean processHead(HttpServletRequest request,
1041: HttpServletResponse response) throws IOException,
1042: ServletException {
1043:
1044: String path = getRelativePathSAKAI(request);
1045:
1046: if ((path == null) || prohibited(path)
1047: || path.toUpperCase().startsWith("/WEB-INF")
1048: || path.toUpperCase().startsWith("/META-INF")) {
1049: response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1050: return false;
1051: }
1052:
1053: // Retrieve the resources
1054: DirContextSAKAI resources = getResourcesSAKAI();
1055: ResourceInfoSAKAI resourceInfo = new ResourceInfoSAKAI(path,
1056: resources);
1057:
1058: if (!resourceInfo.exists) {
1059: response.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1060: return false;
1061: }
1062:
1063: if (!resourceInfo.collection) {
1064: response.setDateHeader("Last-Modified", resourceInfo.date);
1065: }
1066:
1067: // Find content type by looking at the file suffix
1068: // We should probably make this something which is done within the service
1069: // rather than each tool
1070:
1071: String contentType = getServletContext().getMimeType(
1072: resourceInfo.path);
1073:
1074: // if (M_log.isDebugEnabled()) M_log.debug("Default serveResource contentType = " + contentType);
1075: if (contentType != null) {
1076: response.setContentType(contentType);
1077: }
1078:
1079: long contentLength = resourceInfo.length;
1080: if ((!resourceInfo.collection) && (contentLength >= 0)) {
1081: response.setContentLength((int) contentLength);
1082: }
1083: return true;
1084: }
1085:
1086: /**
1087: * OPTIONS Method.
1088: */
1089: protected void doOptions(HttpServletRequest req,
1090: HttpServletResponse resp) throws ServletException,
1091: IOException {
1092:
1093: String path = getRelativePathSAKAI(req);
1094:
1095: resp.addHeader("DAV", "1,2");
1096: String methodsAllowed = null;
1097:
1098: // Retrieve the resources
1099: DirContextSAKAI resources = getResourcesSAKAI();
1100:
1101: if (resources == null) {
1102: M_log.warn("SAKAIDAV doOptions ERROR Resources is null");
1103: resp
1104: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1105: return;
1106: }
1107:
1108: ResourceInfoSAKAI resourceInfo = new ResourceInfoSAKAI(path,
1109: resources);
1110:
1111: boolean exists = true;
1112: Object object = null;
1113: try {
1114: object = resources.lookup(path);
1115: } catch (NamingException e) {
1116: exists = false;
1117: }
1118:
1119: if (!exists) {
1120: methodsAllowed = "OPTIONS, MKCOL, PUT, LOCK, UNLOCK";
1121: resp.addHeader("Allow", methodsAllowed);
1122: return;
1123: }
1124:
1125: methodsAllowed = "OPTIONS, GET, HEAD, POST, DELETE, PROPFIND, COPY, MOVE, LOCK, UNLOCK";
1126:
1127: // don't know why, but this instanceof test doesn't work
1128: // if (!(object instanceof DirContext)) {
1129: if (!resourceInfo.collection) {
1130: methodsAllowed += ", PUT";
1131: } else {
1132: methodsAllowed += ", MKCOL";
1133: }
1134:
1135: resp.addHeader("Allow", methodsAllowed);
1136:
1137: resp.addHeader("MS-Author-Via", "DAV");
1138:
1139: }
1140:
1141: // Wrappers to make SAKAI look almost like a DirContext - This may be replaced as
1142: // we move this to an OKI style framework
1143:
1144: public class DirContextSAKAI {
1145: protected String path;
1146:
1147: private DirContext myDC;
1148:
1149: public boolean isCollection;
1150:
1151: private ContentCollection collection;
1152:
1153: private boolean isRootCollection;
1154:
1155: public Object lookup(String id) throws NamingException {
1156: path = id;
1157:
1158: // resource or collection? check the properties (also finds bad id and checks permissions)
1159: isCollection = false;
1160: isRootCollection = false;
1161: try {
1162: ResourceProperties props;
1163:
1164: path = fixDirPathSAKAI(path);
1165:
1166: // Do not allow access to /attachments
1167:
1168: if (path.startsWith("/attachments")) {
1169: M_log
1170: .info("DirContextSAKAI.lookup - You do not have permission to view this area "
1171: + path);
1172: throw new NamingException();
1173: }
1174:
1175: props = ContentHostingService
1176: .getProperties(adjustId(path));
1177:
1178: isCollection = props
1179: .getBooleanProperty(ResourceProperties.PROP_IS_COLLECTION);
1180:
1181: if (isCollection) {
1182: // if (M_log.isDebugEnabled()) M_log.debug("DirContextSAKAI.lookup getting collection");
1183: collection = ContentHostingService
1184: .getCollection(adjustId(path));
1185: isRootCollection = ContentHostingService
1186: .isRootCollection(adjustId(path));
1187: }
1188: } catch (PermissionException e) {
1189: M_log
1190: .debug("DirContextSAKAI.lookup - You do not have permission to view this resource "
1191: + path);
1192: throw new NamingException();
1193: } catch (IdUnusedException e) {
1194: M_log
1195: .debug("DirContextSAKAI.lookup - This resource does not exist: "
1196: + id);
1197: throw new NamingException();
1198: } catch (EntityPropertyNotDefinedException e) {
1199: M_log
1200: .warn("DirContextSAKAI.lookup - This resource is empty: "
1201: + id);
1202: throw new NamingException();
1203: } catch (EntityPropertyTypeException e) {
1204: M_log
1205: .warn("DirContextSAKAI.lookup - This resource has a EntityPropertyTypeException exception: "
1206: + id);
1207: throw new NamingException();
1208: } catch (TypeException e) {
1209: M_log
1210: .warn("DirContextSAKAI.lookup - This resource has a type exception: "
1211: + id);
1212: throw new NamingException();
1213: }
1214:
1215: // for resources
1216:
1217: // if (M_log.isDebugEnabled()) M_log.debug("DirContextSAKAI.lookup is collection " + path);
1218: // if (M_log.isDebugEnabled()) M_log.debug(" isCollection = " + isCollection);
1219: // if (M_log.isDebugEnabled()) M_log.debug(" isRootCollection = " + isRootCollection);
1220: return myDC;
1221: }
1222:
1223: public Iterator list(String id) {
1224: try {
1225: Object object = lookup(id);
1226: } catch (Exception e) {
1227: return null;
1228: }
1229: if (M_log.isDebugEnabled())
1230: M_log
1231: .debug("DirContextSAKAI.list getting collection members and iterator");
1232: List members = collection.getMemberResources();
1233: Iterator it = members.iterator();
1234: return it;
1235: }
1236: }
1237:
1238: public class ResourceInfoSAKAI {
1239: private DirContextSAKAI resources;
1240:
1241: private ResourceProperties props = null;
1242:
1243: private String path;
1244:
1245: public boolean collection;
1246:
1247: public boolean exists;
1248:
1249: public long length;
1250:
1251: public String httpDate;
1252:
1253: public long creationDate;
1254:
1255: public String MIMEType;
1256:
1257: public long modificationDate;
1258:
1259: public long date; // From DirContext
1260:
1261: public String displayName;
1262:
1263: public String resourceName; // The "non-display" name
1264:
1265: public String resourceLink; // The resource link (within SAKAI)
1266:
1267: public String eTag; // The eTag
1268:
1269: public ResourceInfoSAKAI(String our_path,
1270: DirContextSAKAI parent_resources) {
1271: path = our_path;
1272: exists = false;
1273:
1274: resources = parent_resources;
1275: // if (M_log.isDebugEnabled()) M_log.debug("ResourceInfoSAKAI Constructor path = " + path);
1276:
1277: // resource or collection? check the properties (also finds bad id and checks permissions)
1278: collection = false;
1279: try {
1280: ResourceProperties props;
1281: Entity mbr;
1282:
1283: path = fixDirPathSAKAI(path); // Add slash as necessary
1284:
1285: props = ContentHostingService
1286: .getProperties(adjustId(path));
1287:
1288: collection = props
1289: .getBooleanProperty(ResourceProperties.PROP_IS_COLLECTION);
1290:
1291: resourceName = props
1292: .getProperty(ResourceProperties.PROP_DISPLAY_NAME);
1293: displayName = props
1294: .getPropertyFormatted(ResourceProperties.PROP_DISPLAY_NAME);
1295: exists = true;
1296:
1297: if (!collection) {
1298: mbr = ContentHostingService
1299: .getResource(adjustId(path));
1300: // Props for a file is OK from above
1301: length = ((ContentResource) mbr).getContentLength();
1302: MIMEType = ((ContentResource) mbr).getContentType();
1303: eTag = ((ContentResource) mbr).getId();
1304: } else {
1305: mbr = ContentHostingService
1306: .getCollection(adjustId(path));
1307: props = mbr.getProperties();
1308: eTag = our_path;
1309: }
1310: modificationDate = props.getTimeProperty(
1311: ResourceProperties.PROP_MODIFIED_DATE)
1312: .getTime();
1313: eTag = modificationDate + "+" + eTag;
1314: if (M_log.isDebugEnabled())
1315: M_log.debug("Path=" + path + " eTag=" + eTag);
1316: creationDate = props.getTimeProperty(
1317: ResourceProperties.PROP_CREATION_DATE)
1318: .getTime();
1319: resourceLink = mbr.getUrl();
1320: String resourceName = getResourceNameSAKAI(mbr);
1321:
1322: } catch (PermissionException e) {
1323: M_log
1324: .debug("ResourceInfoSAKAI - You do not have permission to view this resource "
1325: + path);
1326: } catch (IdUnusedException e) {
1327: M_log
1328: .debug("ResourceInfoSAKAI - This resource does not exist "
1329: + path);
1330: } catch (EntityPropertyNotDefinedException e) {
1331: M_log.warn("ResourceInfoSAKAI - This resource is empty"
1332: + path);
1333: } catch (EntityPropertyTypeException e) {
1334: M_log
1335: .warn("ResourceInfoSAKAI - EntityPropertyType Exception "
1336: + path);
1337: } catch (TypeException e) {
1338: M_log
1339: .warn("ResourceInfoSAKAI - Type Exception "
1340: + path);
1341: }
1342:
1343: httpDate = getHttpDate(modificationDate);
1344: if (creationDate == 0)
1345: creationDate = modificationDate;
1346: date = modificationDate;
1347: /*
1348: * if (M_log.isDebugEnabled()) M_log.debug("ResourceInfoSAKAI path="+path); if (M_log.isDebugEnabled()) M_log.debug(" collection=" + collection); if (M_log.isDebugEnabled()) M_log.debug(" length=" + length); if (M_log.isDebugEnabled())
1349: * M_log.debug(" ISOCreationDate=" + creationDate + " ISO= " + getISOCreationDate(creationDate)); if (M_log.isDebugEnabled()) M_log.debug(" ModificationDate=" + modificationDate + " ISO= " + getISOCreationDate(modificationDate)); if
1350: * (M_log.isDebugEnabled()) M_log.debug(" MIMEType=" + MIMEType); if (M_log.isDebugEnabled()) M_log.debug(" httpDate=" + httpDate); if (M_log.isDebugEnabled()) M_log.debug(" resourceName="+resourceName); if (M_log.isDebugEnabled())
1351: * M_log.debug(" resourceLink="+resourceLink); if (M_log.isDebugEnabled()) M_log.debug(" displayName=" + displayName);
1352: */
1353: }
1354: }
1355:
1356: public DirContextSAKAI getResourcesSAKAI() {
1357: return new DirContextSAKAI();
1358: }
1359:
1360: public String fixDirPathSAKAI(String path) {
1361:
1362: String tmpPath = path;
1363:
1364: ResourceProperties props;
1365: try {
1366: props = ContentHostingService
1367: .getProperties(adjustId(tmpPath));
1368: } catch (IdUnusedException e) {
1369: if (!tmpPath.endsWith("/")) { // We will add a slash and try again
1370: String newPath = tmpPath + "/";
1371: try {
1372: props = ContentHostingService
1373: .getProperties(adjustId(newPath));
1374: tmpPath = newPath;
1375: } catch (Exception x) {
1376: } // Ignore everything
1377: }
1378: } catch (Exception e) {
1379: } // Ignore all other exceptions
1380: return tmpPath;
1381: }
1382:
1383: /**
1384: * POST Method.
1385: */
1386: protected void doPost(HttpServletRequest req,
1387: HttpServletResponse resp) throws ServletException,
1388: IOException {
1389:
1390: String path = getRelativePathSAKAI(req);
1391:
1392: doContent(path, req, resp);
1393: }
1394:
1395: /**
1396: * GET Method.
1397: */
1398: protected void doGet(HttpServletRequest req,
1399: HttpServletResponse resp) throws ServletException,
1400: IOException {
1401:
1402: String path = getRelativePathSAKAI(req);
1403:
1404: doContent(path, req, resp);
1405: }
1406:
1407: protected int countSlashes(String s) {
1408: int count = 0;
1409: int loc = s.indexOf('/');
1410:
1411: while (loc >= 0) {
1412: count++;
1413: loc++;
1414: loc = s.indexOf('/', loc);
1415: }
1416:
1417: return count;
1418: }
1419:
1420: // id is known to be a collection
1421: private void doDirectory(String id, HttpServletRequest req,
1422: HttpServletResponse res) {
1423: // OK, it's a collection and we can read it. Do a listing.
1424: // System.out.println("got to final check");
1425:
1426: if (prohibited(id))
1427: return;
1428:
1429: String uri = req.getRequestURI();
1430: PrintWriter out = null;
1431:
1432: // don't set the writer until we verify that
1433: // getallresources is going to work.
1434:
1435: try {
1436: ContentCollection x = ContentHostingService
1437: .getCollection(adjustId(id));
1438:
1439: // I want to use relative paths in the listing,
1440: // so we need to redirect if there's no trailing /
1441: // for the usual reasons.
1442: // --this doesn't actually work. without / it doesn't get here
1443: if (!uri.endsWith("/")) {
1444: // System.out.println("need redirect");
1445: try {
1446: res.sendRedirect(uri + "/");
1447: // System.out.println("redirect ok");
1448: return;
1449: } catch (IOException ignore) {
1450: // System.out.println("redirect failed");
1451: return;
1452: }
1453: }
1454:
1455: List xl = x.getMembers();
1456: Collections.sort(xl);
1457: Iterator xi = xl.iterator();
1458:
1459: res.setContentType("text/html; charset=UTF-8");
1460:
1461: out = res.getWriter();
1462: out
1463: .println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
1464: out.println("<html><head>");
1465: String webappRoot = ServerConfigurationService
1466: .getServerUrl();
1467: out
1468: .println("<link href=\""
1469: + webappRoot
1470: + "/css/default.css\" type=\"text/css\" rel=\"stylesheet\" media=\"screen\" />");
1471: out.println("<STYLE type=\"text/css\">");
1472: out.println("<!--");
1473: out.println("td {padding-right: .5em}");
1474: out.println("-->");
1475: out.println("</STYLE>");
1476: out.println("</head><body>");
1477: out.println("<div style=\"padding: 16px\">");
1478: out.println("<h2>Contents of /dav" + id + "</h2>");
1479: out.println("<table>");
1480:
1481: // show .. if not already there. we don't get aliases
1482: // so this is
1483: // /group/db5a4d0c-3dfd-4d10-8018-41db42ac7c8b/
1484: // /user/hedrick/
1485:
1486: int slashes = countSlashes(adjustId(id));
1487:
1488: if (slashes > 3) {
1489: // go up a level
1490: //String uplev = id.substring(0, id.length() - 1);
1491: //uplev = uplev.substring(0, uplev.lastIndexOf('/') + 1);
1492: out
1493: .println("<tr><td><a href=\"..\">Up one level</a></td><td><b>Folder</b>"
1494: + "</td><td>"
1495: + "</td><td>"
1496: + "</td><td>" + "</td></tr>");
1497:
1498: }
1499:
1500: while (xi.hasNext()) {
1501: String xs = (String) xi.next();
1502: String xss = xs.substring(adjustId(id).length());
1503:
1504: if (xss.endsWith("/")) {
1505: ContentCollection nextres = ContentHostingService
1506: .getCollection(adjustId(xs));
1507: ResourceProperties properties = nextres
1508: .getProperties();
1509: if (doProtected
1510: && xs.toLowerCase().indexOf("/protected") >= 0) {
1511: if (!ContentHostingService
1512: .allowAddCollection(adjustId(xs))) {
1513: continue;
1514: }
1515: }
1516: out.println("<tr><td><a href=\""
1517: + Validator.escapeUrl(xss) + "\">"
1518: + Validator.escapeHtml(xss)
1519: + "</a></td><td><b>Folder</b>"
1520: + "</td><td>" + "</td><td>" + "</td><td>"
1521: + "</td></tr>");
1522: } else
1523: try {
1524: ContentResource nextres = ContentHostingService
1525: .getResource(adjustId(xs));
1526: ResourceProperties properties = nextres
1527: .getProperties();
1528:
1529: long filesize = ((nextres.getContentLength() - 1) / 1024) + 1;
1530: String createdBy = getUserPropertyDisplayName(
1531: properties,
1532: ResourceProperties.PROP_CREATOR);
1533: Time modTime = properties
1534: .getTimeProperty(ResourceProperties.PROP_MODIFIED_DATE);
1535: String modifiedTime = modTime
1536: .toStringLocalShortDate()
1537: + " " + modTime.toStringLocalShort();
1538: String filetype = nextres.getContentType();
1539: out.println("<tr><td><a href=\""
1540: + Validator.escapeUrl(xss) + "\">"
1541: + Validator.escapeHtml(xss)
1542: + "</a></td><td>" + filesize
1543: + "</td><td>" + createdBy + "</td><td>"
1544: + filetype + "</td><td>" + modifiedTime
1545: + "</td></tr>");
1546: } catch (Throwable ignore) {
1547: out.println("<tr><td><a href=\""
1548: + Validator.escapeUrl(xss) + "\">"
1549: + Validator.escapeHtml(xss)
1550: + "</a></td><td>" + "</td><td>"
1551: + "</td><td>" + "</td><td>"
1552: + "</td></tr>");
1553:
1554: }
1555: }
1556: } catch (Throwable ignore) {
1557: }
1558: if (out != null)
1559: out.println("</table></div></body></html>");
1560: }
1561:
1562: /**
1563: * Handle requests for content, resources ONLY
1564: *
1565: * @param id
1566: * The local resource id.
1567: * @param res
1568: * The http servlet response object.
1569: * @return any error message, or null if all went well.
1570: */
1571: private String doContent(String id, HttpServletRequest req,
1572: HttpServletResponse res) {
1573: if (prohibited(id))
1574: return "You do not have permission to view this resource";
1575:
1576: // resource or collection? check the properties (also finds bad id and checks permissions)
1577: boolean isCollection = false;
1578: try {
1579: ResourceProperties props = ContentHostingService
1580: .getProperties(adjustId(id));
1581: isCollection = props
1582: .getBooleanProperty(ResourceProperties.PROP_IS_COLLECTION);
1583: } catch (PermissionException e) {
1584: return "You do not have permission to view this resource";
1585: } catch (IdUnusedException e) {
1586: return "This resource does not exist";
1587: } catch (EntityPropertyNotDefinedException e) {
1588: return "This resource does not exist";
1589: } catch (EntityPropertyTypeException e) {
1590: return "This resource does not exist";
1591: }
1592:
1593: // for resources
1594: if (!isCollection) {
1595: if (M_log.isDebugEnabled())
1596: M_log.debug("SAKAIAccess doContent is resource " + id);
1597: try {
1598: ContentResource resource = ContentHostingService
1599: .getResource(adjustId(id));
1600: long len = resource.getContentLength();
1601: String contentType = resource.getContentType();
1602: byte[] content = resource.getContent();
1603:
1604: if (content == null) {
1605: return "Empty resource";
1606: }
1607:
1608: // for URL content type, encode a redirect to the body URL
1609: if (contentType
1610: .equalsIgnoreCase(ResourceProperties.TYPE_URL)) {
1611: res.sendRedirect(new String(content));
1612: } else {
1613: if (!processHead(req, res))
1614: return "Error setting header values";
1615: OutputStream out = res.getOutputStream();
1616: // now set in processHead
1617: // res.setContentType(contentType);
1618: out.write(content);
1619: out.flush();
1620: }
1621: } catch (Throwable e) {
1622: // M_log.warn(this + ".doContent(): exception: id: " + id + " : " + e.toString());
1623: return e.toString();
1624: }
1625: }
1626:
1627: // for collections
1628: else {
1629: doDirectory(id, req, res);
1630: }
1631:
1632: // no errors
1633: return null;
1634:
1635: } // doContent
1636:
1637: // Sometimes we are the root applet and other times, we are a sub-applet
1638: // We have to trim off the part of the path which gets to us
1639: // Also we have to deal with the fact that SAKAI likes collections with trailing slashes
1640: // while http like collections without trailing slashes
1641: // So, we take a quick look to see if the directory does not exist without the slash
1642: // and if it does not exist without the slash, we peek to see if it exists with the slash.
1643: // if so, we tack the slash on.
1644:
1645: public String getRelativePathSAKAI(HttpServletRequest req) {
1646: String path = req.getPathInfo();
1647: // if (path != null)
1648: // {
1649: // try
1650: // {
1651: // path = ParameterParser.convertFromRawBytesToUTF8(path);
1652: // }
1653: // catch (UnsupportedEncodingException e)
1654: // {
1655: // M_log.warn(e);
1656: // }
1657: // catch (CharacterCodingException e)
1658: // {
1659: // M_log.warn(e);
1660: // }
1661: // }
1662:
1663: if (path == null)
1664: path = "/";
1665: if (M_log.isDebugEnabled())
1666: M_log.debug("getRelativePathSAKAI = " + path);
1667: return path;
1668:
1669: /*
1670: * String tmpPath = getRelativePath(req); // if (M_log.isDebugEnabled()) M_log.debug("getRelativePathSAKAI = " + tmpPath); // Remove the parent path if ( tmpPath.length() >= 4 ) { if(tmpPath.equalsIgnoreCase("/dav")) { tmpPath = "/"; } else if
1671: * (tmpPath.substring(0,4).equalsIgnoreCase("/dav") ) { tmpPath = "/" + tmpPath.substring(4); } // if (M_log.isDebugEnabled()) M_log.debug("New Relative Path = " + tmpPath); } return tmpPath;
1672: */
1673: } // getRelativePathSAKAI
1674:
1675: /**
1676: * getResourceNameSAKAI - Needs to become a method of resource returns the internal name for a resource.
1677: */
1678:
1679: public String getResourceNameSAKAI(Entity mbr) {
1680: String idx = mbr.getId();
1681: ResourceProperties props = mbr.getProperties();
1682: String resourceName = props
1683: .getProperty(ResourceProperties.PROP_DISPLAY_NAME);
1684:
1685: if (idx.startsWith("/") && idx.endsWith("/")
1686: && idx.length() > 3) {
1687: int lastSlash = idx.lastIndexOf("/", idx.length() - 2);
1688: if (lastSlash > 0 && lastSlash + 1 <= idx.length() - 2) {
1689: // if (M_log.isDebugEnabled()) M_log.debug("ls1="+(lastSlash+1)+" idl="+(idx.length()-1));
1690: resourceName = idx.substring(lastSlash + 1, idx
1691: .length() - 1);
1692: }
1693:
1694: } else if (idx.startsWith("/") && !idx.endsWith("/")
1695: && idx.length() > 2) {
1696: int lastSlash = idx.lastIndexOf("/");
1697: if (lastSlash > -1) {
1698: // if (M_log.isDebugEnabled()) M_log.debug("ls="+lastSlash);
1699: resourceName = idx.substring(lastSlash + 1);
1700: }
1701: }
1702:
1703: String parts[] = StringUtil.split(idx, Entity.SEPARATOR);
1704: if (parts.length == 4 && parts[1].equals("group-user")) {
1705: try {
1706: // if successful, the context is already a valid user EID
1707: UserDirectoryService.getUserByEid(parts[3]);
1708: } catch (UserNotDefinedException tryId) {
1709: try {
1710: // try using it as an ID
1711: resourceName = UserDirectoryService
1712: .getUserEid(parts[3]);
1713: } catch (UserNotDefinedException notId) {
1714: // if context was not a valid ID, leave it alone
1715: }
1716: }
1717: }
1718:
1719: // if (M_log.isDebugEnabled()) M_log.debug("getResourceNameSAKAI resourceName="+resourceName);
1720: // if (M_log.isDebugEnabled()) M_log.debug(" idx="+idx);
1721: // if (M_log.isDebugEnabled()) M_log.debug(" displayName = " + props.getProperty(props.PROP_DISPLAY_NAME));
1722:
1723: return resourceName;
1724: }
1725:
1726: /**
1727: * PROPFIND Method.
1728: */
1729: protected void doPropfind(HttpServletRequest req,
1730: HttpServletResponse resp) throws ServletException,
1731: IOException {
1732:
1733: String path = getRelativePathSAKAI(req);
1734:
1735: if (path.endsWith("/"))
1736: path = path.substring(0, path.length() - 1);
1737:
1738: if ((path.toUpperCase().startsWith("/WEB-INF"))
1739: || (path.toUpperCase().startsWith("/META-INF"))
1740: || prohibited(path)) {
1741: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
1742: return;
1743: }
1744:
1745: // Properties which are to be displayed.
1746: Vector properties = null;
1747: // Propfind depth
1748: int depth = INFINITY;
1749: // Propfind type
1750: int type = FIND_ALL_PROP;
1751:
1752: String depthStr = req.getHeader("Depth");
1753:
1754: if (depthStr == null) {
1755: depth = INFINITY;
1756: } else {
1757: if (depthStr.equals("0")) {
1758: depth = 0;
1759: } else if (depthStr.equals("1")) {
1760: depth = 1;
1761: } else if (depthStr.equals("infinity")) {
1762: depth = INFINITY;
1763: }
1764: }
1765:
1766: Node propNode = null;
1767:
1768: DocumentBuilder documentBuilder = getDocumentBuilder();
1769:
1770: // be careful how we get content, as we've had hangs in mod_jk
1771: // Rather than passing the XML parser a stream on the network
1772: // input, we read it into a buffer and pass them a stream
1773: // on the buffer. This is an experiment to see if it fixes
1774: // the hangs.
1775:
1776: // Note that getContentLength can return -1. As everyone seems
1777: // to use the content-length header, ignore that case for now
1778: // It is strongly discouraged by the spec.
1779:
1780: int contentLength = req.getContentLength();
1781:
1782: if (contentLength > 0) {
1783:
1784: byte[] byteContent = new byte[contentLength];
1785: InputStream inputStream = req.getInputStream();
1786:
1787: int lenRead = 0;
1788:
1789: try {
1790: while (lenRead < contentLength) {
1791: int read = inputStream.read(byteContent, lenRead,
1792: contentLength - lenRead);
1793: if (read <= 0)
1794: break;
1795: lenRead += read;
1796: }
1797: } catch (Exception ignore) {
1798: }
1799: // if anything goes wrong, we treat it as find all props
1800:
1801: // Parse the input XML to see what they really want
1802: if (lenRead > 0)
1803: try {
1804: InputStream is = new ByteArrayInputStream(
1805: byteContent, 0, lenRead);
1806: // System.out.println("have bytes");
1807: Document document = documentBuilder
1808: .parse(new InputSource(is));
1809:
1810: // Get the root element of the document
1811: Element rootElement = document.getDocumentElement();
1812: NodeList childList = rootElement.getChildNodes();
1813: // System.out.println("have nodes " + childList.getLength());
1814:
1815: for (int i = 0; i < childList.getLength(); i++) {
1816: Node currentNode = childList.item(i);
1817: // System.out.println("looking at node " + currentNode.getNodeName());
1818: switch (currentNode.getNodeType()) {
1819: case Node.TEXT_NODE:
1820: break;
1821: case Node.ELEMENT_NODE:
1822: if (currentNode.getNodeName().endsWith(
1823: "prop")) {
1824: type = FIND_BY_PROPERTY;
1825: propNode = currentNode;
1826: }
1827: if (currentNode.getNodeName().endsWith(
1828: "propname")) {
1829: type = FIND_PROPERTY_NAMES;
1830: }
1831: if (currentNode.getNodeName().endsWith(
1832: "allprop")) {
1833: type = FIND_ALL_PROP;
1834: }
1835: break;
1836: }
1837: }
1838: } catch (Exception ignore) {
1839: }
1840: // again, in case of exception, we'll have the default
1841: // FIND_ALL_PROP
1842: }
1843:
1844: // System.out.println("Find type " + type);
1845:
1846: if (type == FIND_BY_PROPERTY) {
1847: properties = new Vector();
1848: NodeList childList = propNode.getChildNodes();
1849:
1850: for (int i = 0; i < childList.getLength(); i++) {
1851: Node currentNode = childList.item(i);
1852: switch (currentNode.getNodeType()) {
1853: case Node.TEXT_NODE:
1854: break;
1855: case Node.ELEMENT_NODE:
1856: String nodeName = currentNode.getNodeName();
1857: String propertyName = null;
1858: if (nodeName.indexOf(':') != -1) {
1859: propertyName = nodeName.substring(nodeName
1860: .indexOf(':') + 1);
1861: } else {
1862: propertyName = nodeName;
1863: }
1864: // href is a live property which is handled differently
1865: properties.addElement(propertyName);
1866: break;
1867: }
1868: }
1869:
1870: }
1871:
1872: // Retrieve the resources
1873: DirContextSAKAI resources = getResourcesSAKAI();
1874:
1875: if (resources == null) {
1876: resp
1877: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1878: return;
1879: }
1880:
1881: // Point the resource object at a particular path and catch the error if necessary.
1882:
1883: boolean exists = true;
1884: Object object = null;
1885: try {
1886: object = resources.lookup(path);
1887: } catch (NamingException e) {
1888: exists = false;
1889: int slash = path.lastIndexOf('/');
1890: if (slash != -1) {
1891: String parentPath = path.substring(0, slash);
1892: Vector currentLockNullResources = (Vector) lockNullResources
1893: .get(parentPath);
1894: if (currentLockNullResources != null) {
1895: Enumeration lockNullResourcesList = currentLockNullResources
1896: .elements();
1897: while (lockNullResourcesList.hasMoreElements()) {
1898: String lockNullPath = (String) lockNullResourcesList
1899: .nextElement();
1900: if (lockNullPath.equals(path)) {
1901: resp
1902: .setStatus(SakaidavStatus.SC_MULTI_STATUS);
1903: resp
1904: .setContentType("text/xml; charset=UTF-8");
1905: // Create multistatus object
1906: XMLWriter generatedXML = new XMLWriter(resp
1907: .getWriter());
1908: generatedXML.writeXMLHeader();
1909: generatedXML
1910: .writeElement(
1911: null,
1912: "multistatus"
1913: + generateNamespaceDeclarations(),
1914: XMLWriter.OPENING);
1915: parseLockNullProperties(req, generatedXML,
1916: lockNullPath, type, properties);
1917: generatedXML.writeElement(null,
1918: "multistatus", XMLWriter.CLOSING);
1919: generatedXML.sendData();
1920: return;
1921: }
1922: }
1923: }
1924: }
1925: }
1926:
1927: if (!exists) {
1928: resp.sendError(HttpServletResponse.SC_NOT_FOUND, "/dav"
1929: + path);
1930: return;
1931: }
1932:
1933: resp.setStatus(SakaidavStatus.SC_MULTI_STATUS);
1934:
1935: resp.setContentType("text/xml; charset=UTF-8");
1936:
1937: // Create multistatus object
1938: XMLWriter generatedXML = new XMLWriter(resp.getWriter());
1939: generatedXML.writeXMLHeader();
1940:
1941: generatedXML.writeElement(null, "multistatus"
1942: + generateNamespaceDeclarations(), XMLWriter.OPENING);
1943:
1944: if (depth == 0) {
1945: parseProperties(req, resources, generatedXML, path, type,
1946: properties);
1947: } else {
1948: // The stack always contains the object of the current level
1949: Stack stack = new Stack();
1950: stack.push(path);
1951:
1952: // Stack of the objects one level below
1953: Stack stackBelow = new Stack();
1954:
1955: while ((!stack.isEmpty()) && (depth >= 0)) {
1956:
1957: String currentPath = (String) stack.pop();
1958:
1959: try {
1960: // if (M_log.isDebugEnabled()) M_log.debug("Lookup currentPath="+currentPath);
1961: object = resources.lookup(currentPath);
1962: } catch (NamingException e) {
1963: continue;
1964: }
1965:
1966: parseProperties(req, resources, generatedXML,
1967: currentPath, type, properties);
1968:
1969: if ((resources.isCollection) && (depth > 0)) {
1970:
1971: Iterator it = resources.list(currentPath);
1972: while (it.hasNext()) {
1973: Entity mbr = (Entity) it.next();
1974: String resourceName = getResourceNameSAKAI(mbr);
1975:
1976: String newPath = currentPath;
1977: if (!(newPath.endsWith("/")))
1978: newPath += "/";
1979: newPath += resourceName;
1980: if (!(newPath.toLowerCase().indexOf(
1981: "/protected") >= 0 && !ContentHostingService
1982: .allowAddCollection(newPath)))
1983: stackBelow.push(newPath);
1984: // if (M_log.isDebugEnabled()) M_log.debug("SAKAI found resource " + newPath);
1985: }
1986:
1987: // Displaying the lock-null resources present in that
1988: // collection
1989: String lockPath = currentPath;
1990: if (lockPath.endsWith("/"))
1991: lockPath = lockPath.substring(0, lockPath
1992: .length() - 1);
1993: Vector currentLockNullResources = (Vector) lockNullResources
1994: .get(lockPath);
1995: if (currentLockNullResources != null) {
1996: Enumeration lockNullResourcesList = currentLockNullResources
1997: .elements();
1998: while (lockNullResourcesList.hasMoreElements()) {
1999: String lockNullPath = (String) lockNullResourcesList
2000: .nextElement();
2001:
2002: parseLockNullProperties(req, generatedXML,
2003: lockNullPath, type, properties);
2004: }
2005: }
2006:
2007: }
2008:
2009: if (stack.isEmpty()) {
2010: depth--;
2011: stack = stackBelow;
2012:
2013: stackBelow = new Stack();
2014: }
2015: // if (M_log.isDebugEnabled()) M_log.debug("SAKAIDAV.propfind() " + generatedXML.toString());
2016: generatedXML.sendData();
2017: }
2018: }
2019:
2020: generatedXML.writeElement(null, "multistatus",
2021: XMLWriter.CLOSING);
2022: // if (M_log.isDebugEnabled()) M_log.debug("SAKAIDAV.propfind() at end:" + generatedXML.toString());
2023: generatedXML.sendData();
2024:
2025: }
2026:
2027: /**
2028: * PROPPATCH Method.
2029: */
2030: protected void doProppatch(HttpServletRequest req,
2031: HttpServletResponse resp) throws ServletException,
2032: IOException {
2033:
2034: if (readOnly) {
2035: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2036: return;
2037: }
2038:
2039: if (isLocked(req)) {
2040: resp.sendError(SakaidavStatus.SC_LOCKED);
2041: return;
2042: }
2043:
2044: resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
2045:
2046: }
2047:
2048: protected String justName(String str) {
2049: try {
2050: // Note: there may be a trailing separator
2051: int pos = str.lastIndexOf("/", str.length() - 2);
2052: String rv = str.substring(pos + 1);
2053: if (rv.endsWith("/")) {
2054: rv = rv.substring(0, rv.length() - 1);
2055: }
2056: return rv;
2057: } catch (Throwable t) {
2058: return str;
2059: }
2060: }
2061:
2062: /**
2063: * MKCOL Method.
2064: */
2065: protected void doMkcol(HttpServletRequest req,
2066: HttpServletResponse resp) throws ServletException,
2067: IOException {
2068:
2069: if (readOnly) {
2070: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2071: return;
2072: }
2073:
2074: if (isLocked(req)) {
2075: resp.sendError(SakaidavStatus.SC_LOCKED);
2076: return;
2077: }
2078:
2079: String path = getRelativePathSAKAI(req);
2080: if (prohibited(path)
2081: || (path.toUpperCase().startsWith("/WEB-INF"))
2082: || (path.toUpperCase().startsWith("/META-INF"))) {
2083: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2084: return;
2085: }
2086:
2087: String name = justName(path);
2088:
2089: if ((name.toUpperCase().startsWith("/WEB-INF"))
2090: || (name.toUpperCase().startsWith("/META-INF"))) {
2091: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2092: return;
2093: }
2094:
2095: // Check to see if collection already exists
2096: try {
2097: boolean isCollection = ContentHostingService.getProperties(
2098: adjustId(path)).getBooleanProperty(
2099: ResourceProperties.PROP_IS_COLLECTION);
2100:
2101: // if the path already exists and it is a collection there is nothing to do
2102: // if it exists and is a resource, we remove it to make room for the collection
2103: if (isCollection) {
2104: return;
2105: } else {
2106: ContentHostingService.removeResource(adjustId(path));
2107: }
2108: } catch (PermissionException e) {
2109: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2110: return;
2111: } catch (InUseException e) {
2112: resp.sendError(SakaidavStatus.SC_FORBIDDEN); // %%%
2113: return;
2114: } catch (IdUnusedException e) {
2115: // Resource not found (this is actually the normal case
2116: } catch (EntityPropertyNotDefinedException e) {
2117: M_log
2118: .warn("SAKAIDavServlet.doMkcol() - EntityPropertyNotDefinedException "
2119: + path);
2120: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2121: return;
2122: } catch (EntityPropertyTypeException e) {
2123: M_log.warn("SAKAIDavServlet.doMkcol() - TypeException "
2124: + path);
2125: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2126: return;
2127: } catch (TypeException e) {
2128: M_log.warn("SAKAIDavServlet.doMkcol() - TypeException "
2129: + path);
2130: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2131: return;
2132: }
2133:
2134: // Add the collection
2135:
2136: try {
2137: User user = UserDirectoryService.getCurrentUser();
2138:
2139: TimeBreakdown timeBreakdown = TimeService.newTime()
2140: .breakdownLocal();
2141: String mycopyright = "copyright (c)" + " "
2142: + timeBreakdown.getYear() + ", "
2143: + user.getDisplayName() + ". All Rights Reserved. ";
2144:
2145: ContentCollectionEdit edit = ContentHostingService
2146: .addCollection(adjustId(path));
2147: ResourcePropertiesEdit resourceProperties = edit
2148: .getPropertiesEdit();
2149: resourceProperties.addProperty(
2150: ResourceProperties.PROP_DISPLAY_NAME, name);
2151: resourceProperties.addProperty(
2152: ResourceProperties.PROP_COPYRIGHT, mycopyright);
2153: ContentHostingService.commitCollection(edit);
2154:
2155: ContentCollection collection = ContentHostingService
2156: .addCollection(adjustId(path), resourceProperties);
2157: }
2158:
2159: catch (IdUsedException e) {
2160: // Should not happen because if this esists, we either return or delete above
2161: } catch (IdInvalidException e) {
2162: M_log.warn("SAKAIDavServlet.doMkcol() IdInvalid:"
2163: + e.getMessage());
2164: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2165: return;
2166: } catch (PermissionException e) {
2167: // This is normal
2168: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2169: return;
2170: } catch (InconsistentException e) {
2171: M_log
2172: .warn("SAKAIDavServlet.doMkcol() InconsistentException:"
2173: + e.getMessage());
2174: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2175: return;
2176: }
2177:
2178: resp.setStatus(HttpServletResponse.SC_CREATED);
2179: // Removing any lock-null resource which would be present
2180: lockNullResources.remove(path);
2181:
2182: }
2183:
2184: /**
2185: * DELETE Method.
2186: */
2187: protected void doDelete(HttpServletRequest req,
2188: HttpServletResponse resp) throws ServletException,
2189: IOException {
2190:
2191: if (readOnly) {
2192: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2193: return;
2194: }
2195:
2196: if (isLocked(req)) {
2197: resp.sendError(SakaidavStatus.SC_LOCKED);
2198: return;
2199: }
2200:
2201: deleteResource(req, resp);
2202:
2203: }
2204:
2205: /**
2206: * Process a PUT request for the specified resource.
2207: *
2208: * @param request
2209: * The servlet request we are processing
2210: * @param response
2211: * The servlet response we are creating
2212: * @exception IOException
2213: * if an input/output error occurs
2214: * @exception ServletException
2215: * if a servlet-specified error occurs
2216: */
2217: protected void doPut(HttpServletRequest req,
2218: HttpServletResponse resp) throws ServletException,
2219: IOException {
2220:
2221: // Do not allow files which match patterns specified in properties
2222: if (!isFileNameAllowed(req))
2223: return;
2224:
2225: ResourceProperties oldProps = null;
2226:
2227: boolean newfile = true;
2228:
2229: if (isLocked(req)) {
2230: resp.sendError(SakaidavStatus.SC_LOCKED);
2231: return;
2232: }
2233:
2234: String path = getRelativePathSAKAI(req);
2235:
2236: if (prohibited(path)
2237: || (path.toUpperCase().startsWith("/WEB-INF"))
2238: || (path.toUpperCase().startsWith("/META-INF"))) {
2239: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2240: return;
2241: }
2242:
2243: // Looking for a Content-Range header
2244: if (req.getHeader("Content-Range") != null) {
2245: // No content range header is supported
2246: resp.sendError(SakaidavStatus.SC_NOT_IMPLEMENTED);
2247: }
2248:
2249: String name = justName(path);
2250:
2251: // Database max for id field is 255. If we allow longer, odd things happen
2252: if (path.length() > 254) {
2253: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2254: return;
2255: }
2256:
2257: if ((name.toUpperCase().startsWith("/WEB-INF"))
2258: || (name.toUpperCase().startsWith("/META-INF"))) {
2259: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2260: return;
2261: }
2262:
2263: // Try to delete the resource
2264: try {
2265: // The existing document may be a collection or a file.
2266: boolean isCollection = ContentHostingService.getProperties(
2267: adjustId(path)).getBooleanProperty(
2268: ResourceProperties.PROP_IS_COLLECTION);
2269:
2270: if (isCollection) {
2271: ContentHostingService.removeCollection(adjustId(path));
2272: } else {
2273: // save original properties; we're just updating the file
2274: oldProps = ContentHostingService
2275: .getProperties(adjustId(path));
2276: newfile = false;
2277: ContentHostingService.removeResource(adjustId(path));
2278: }
2279: } catch (PermissionException e) {
2280: // Normal situation
2281: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2282: return;
2283: } catch (InUseException e) {
2284: // Normal situation
2285: resp.sendError(SakaidavStatus.SC_FORBIDDEN); // %%%
2286: return;
2287: } catch (IdUnusedException e) {
2288: // Normal situation - nothing to do
2289: } catch (EntityPropertyNotDefinedException e) {
2290: M_log
2291: .warn("SAKAIDavServlet.doMkcol() - EntityPropertyNotDefinedException "
2292: + path);
2293: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2294: return;
2295: } catch (TypeException e) {
2296: M_log.warn("SAKAIDavServlet.doMkcol() - TypeException "
2297: + path);
2298: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2299: return;
2300: } catch (EntityPropertyTypeException e) {
2301: M_log
2302: .warn("SAKAIDavServlet.doMkcol() - EntityPropertyType "
2303: + path);
2304: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2305: return;
2306: } catch (ServerOverloadException e) {
2307: M_log
2308: .warn("SAKAIDavServlet.doMkcol() - ServerOverloadException "
2309: + path);
2310: resp.sendError(SakaidavStatus.SC_SERVICE_UNAVAILABLE);
2311: return;
2312: }
2313:
2314: // Add the resource
2315:
2316: String contentType = "";
2317: String collectionId = "";
2318: InputStream inputStream = req.getInputStream();
2319: contentType = req.getContentType();
2320: int contentLength = req.getContentLength();
2321: if (M_log.isDebugEnabled())
2322: M_log.debug(" req.contentType() =" + contentType + " len="
2323: + contentLength);
2324:
2325: if (contentLength < 0) {
2326: M_log.warn("SAKAIDavServlet.doPut() content length ("
2327: + contentLength + ") less than zero " + path);
2328: resp.sendError(HttpServletResponse.SC_CONFLICT);
2329: return;
2330: }
2331:
2332: // Convert to byte array for the content service
2333: byte[] byteContent = new byte[contentLength];
2334:
2335: if (contentLength > 0) {
2336: try {
2337: int lenRead = 0;
2338: while (lenRead < contentLength) {
2339: int read = inputStream.read(byteContent, lenRead,
2340: contentLength - lenRead);
2341: if (read <= 0)
2342: break;
2343: lenRead += read;
2344: }
2345: } catch (IOException e) {
2346: M_log.warn("SAKAIDavServlet.doPut() IOException "
2347: + path);
2348: resp.sendError(HttpServletResponse.SC_CONFLICT);
2349: return;
2350: }
2351: }
2352:
2353: if (contentType == null) {
2354: contentType = getServletContext().getMimeType(path);
2355: if (M_log.isDebugEnabled())
2356: M_log.debug("Lookup contentType =" + contentType);
2357: }
2358: if (contentType == null) {
2359: if (M_log.isDebugEnabled())
2360: M_log.debug("Unable to determine contentType");
2361: contentType = ""; // Still cannot figure it out
2362: }
2363:
2364: try {
2365: User user = UserDirectoryService.getCurrentUser();
2366:
2367: TimeBreakdown timeBreakdown = TimeService.newTime()
2368: .breakdownLocal();
2369: String mycopyright = "copyright (c)" + " "
2370: + timeBreakdown.getYear() + ", "
2371: + user.getDisplayName() + ". All Rights Reserved. ";
2372:
2373: // use this code rather than the long form of addResource
2374: // because it doesn't add an extension. Delete doesn't, so we have
2375: // to match, and I'd just as soon be able to create items with no extension anyway
2376: ContentResourceEdit edit = ContentHostingService
2377: .addResource(adjustId(path));
2378: edit.setContentType(contentType);
2379: edit.setContent(byteContent);
2380: ResourcePropertiesEdit p = edit.getPropertiesEdit();
2381:
2382: // copy old props, if any
2383: if (oldProps != null) {
2384: Iterator it = oldProps.getPropertyNames();
2385:
2386: while (it.hasNext()) {
2387: String pname = (String) it.next();
2388:
2389: // skip any live properties
2390: if (!oldProps.isLiveProperty(pname)) {
2391: p.addProperty(pname, oldProps
2392: .getProperty(pname));
2393: }
2394: }
2395: }
2396:
2397: if (newfile) {
2398: p.addProperty(ResourceProperties.PROP_COPYRIGHT,
2399: mycopyright);
2400: p.addProperty(ResourceProperties.PROP_DISPLAY_NAME,
2401: name);
2402: }
2403:
2404: // commit the change
2405: ContentHostingService.commitResource(edit,
2406: NotificationService.NOTI_NONE);
2407:
2408: } catch (IdUsedException e) {
2409: // Should not happen because we deleted above (unless two requests at same time)
2410: M_log.warn("SAKAIDavServlet.doPut() IdUsedException:"
2411: + e.getMessage());
2412:
2413: resp.sendError(HttpServletResponse.SC_CONFLICT);
2414: return;
2415: } catch (IdInvalidException e) {
2416: M_log.warn("SAKAIDavServlet.doPut() IdInvalidException:"
2417: + e.getMessage());
2418: resp.sendError(HttpServletResponse.SC_CONFLICT);
2419: return;
2420: } catch (PermissionException e) {
2421: // Normal
2422: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2423: return;
2424: } catch (OverQuotaException e) {
2425: // Normal %%% what's the proper response for over-quota?
2426: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2427: return;
2428: } catch (InconsistentException e) {
2429: M_log.warn("SAKAIDavServlet.doPut() InconsistentException:"
2430: + e.getMessage());
2431: resp.sendError(HttpServletResponse.SC_CONFLICT);
2432: return;
2433: } catch (ServerOverloadException e) {
2434: M_log
2435: .warn("SAKAIDavServlet.doPut() ServerOverloadException:"
2436: + e.getMessage());
2437: resp.setStatus(SakaidavStatus.SC_SERVICE_UNAVAILABLE);
2438: return;
2439: }
2440: resp.setStatus(HttpServletResponse.SC_CREATED);
2441:
2442: // Removing any lock-null resource which would be present
2443: lockNullResources.remove(path);
2444:
2445: }
2446:
2447: /**
2448: * COPY Method.
2449: */
2450: protected void doCopy(HttpServletRequest req,
2451: HttpServletResponse resp) throws ServletException,
2452: IOException {
2453:
2454: // System.out.println("doCopy called");
2455:
2456: if (readOnly) {
2457: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2458: return;
2459: }
2460:
2461: copyResource(req, resp);
2462:
2463: }
2464:
2465: /**
2466: * MOVE Method.
2467: */
2468: protected void doMove(HttpServletRequest req,
2469: HttpServletResponse resp) throws ServletException,
2470: IOException {
2471:
2472: if (readOnly) {
2473: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2474: return;
2475: }
2476:
2477: if (isLocked(req)) {
2478: resp.sendError(SakaidavStatus.SC_LOCKED);
2479: return;
2480: }
2481:
2482: String path = getRelativePath(req);
2483:
2484: if (copyResource(req, resp)) {
2485: deleteResource(path, req, resp);
2486: }
2487:
2488: /*
2489: * String destinationPath = getDestinationPath(req); if (destinationPath == null) { resp.sendError(SakaidavStatus.SC_BAD_REQUEST); return; } // path = fixDirPathSAKAI(path); destinationPath = fixDirPathSAKAI(destinationPath); //
2490: * System.out.println("doMove source="+path+" dest="+destinationPath); try { ContentHostingService.rename(path,destinationPath); } catch (PermissionException e) { resp.sendError(SakaidavStatus.SC_BAD_REQUEST); return; } catch (InUseException e) {
2491: * resp.sendError(SakaidavStatus.SC_BAD_REQUEST); return; } catch (IdUnusedException e) { // Resource not found (this is actually the normal case) } catch (TypeException e) { M_log.warn("SAKAIDavServlet.doMove() - TypeException "+path);
2492: * resp.sendError(SakaidavStatus.SC_BAD_REQUEST); return; }
2493: */
2494:
2495: }
2496:
2497: /**
2498: * LOCK Method.
2499: */
2500: protected void doLock(HttpServletRequest req,
2501: HttpServletResponse resp) throws ServletException,
2502: IOException {
2503:
2504: if (readOnly) {
2505: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
2506: return;
2507: }
2508:
2509: if (isLocked(req)) {
2510: resp.sendError(SakaidavStatus.SC_LOCKED);
2511: return;
2512: }
2513:
2514: LockInfo lock = new LockInfo();
2515:
2516: // Parsing lock request
2517:
2518: // Parsing depth header
2519:
2520: String depthStr = req.getHeader("Depth");
2521:
2522: if (depthStr == null) {
2523: lock.depth = INFINITY;
2524: } else {
2525: if (depthStr.equals("0")) {
2526: lock.depth = 0;
2527: } else {
2528: lock.depth = INFINITY;
2529: }
2530: }
2531:
2532: // Parsing timeout header
2533:
2534: int lockDuration = DEFAULT_TIMEOUT;
2535: String lockDurationStr = req.getHeader("Timeout");
2536: if (lockDurationStr == null) {
2537: lockDuration = DEFAULT_TIMEOUT;
2538: } else {
2539: if (lockDurationStr.startsWith("Second-")) {
2540: lockDuration = (new Integer(lockDurationStr
2541: .substring(7))).intValue();
2542: } else {
2543: if (lockDurationStr.equalsIgnoreCase("infinity")) {
2544: lockDuration = MAX_TIMEOUT;
2545: } else {
2546: try {
2547: lockDuration = (new Integer(lockDurationStr))
2548: .intValue();
2549: } catch (NumberFormatException e) {
2550: lockDuration = MAX_TIMEOUT;
2551: }
2552: }
2553: }
2554: if (lockDuration == 0) {
2555: lockDuration = DEFAULT_TIMEOUT;
2556: }
2557: if (lockDuration > MAX_TIMEOUT) {
2558: lockDuration = MAX_TIMEOUT;
2559: }
2560: }
2561: lock.expiresAt = System.currentTimeMillis()
2562: + (lockDuration * 1000);
2563:
2564: int lockRequestType = LOCK_CREATION;
2565:
2566: Node lockInfoNode = null;
2567:
2568: DocumentBuilder documentBuilder = getDocumentBuilder();
2569:
2570: try {
2571: Document document = documentBuilder.parse(new InputSource(
2572: req.getInputStream()));
2573:
2574: // Get the root element of the document
2575: Element rootElement = document.getDocumentElement();
2576: lockInfoNode = rootElement;
2577: } catch (Exception e) {
2578: lockRequestType = LOCK_REFRESH;
2579: }
2580:
2581: if (lockInfoNode != null) {
2582:
2583: // Reading lock information
2584:
2585: NodeList childList = lockInfoNode.getChildNodes();
2586: StringWriter strWriter = null;
2587: DOMWriter domWriter = null;
2588:
2589: Node lockScopeNode = null;
2590: Node lockTypeNode = null;
2591: Node lockOwnerNode = null;
2592:
2593: for (int i = 0; i < childList.getLength(); i++) {
2594: Node currentNode = childList.item(i);
2595: switch (currentNode.getNodeType()) {
2596: case Node.TEXT_NODE:
2597: break;
2598: case Node.ELEMENT_NODE:
2599: String nodeName = currentNode.getNodeName();
2600: if (nodeName.endsWith("lockscope")) {
2601: lockScopeNode = currentNode;
2602: }
2603: if (nodeName.endsWith("locktype")) {
2604: lockTypeNode = currentNode;
2605: }
2606: if (nodeName.endsWith("owner")) {
2607: lockOwnerNode = currentNode;
2608: }
2609: break;
2610: }
2611: }
2612:
2613: if (lockScopeNode != null) {
2614:
2615: childList = lockScopeNode.getChildNodes();
2616: for (int i = 0; i < childList.getLength(); i++) {
2617: Node currentNode = childList.item(i);
2618: switch (currentNode.getNodeType()) {
2619: case Node.TEXT_NODE:
2620: break;
2621: case Node.ELEMENT_NODE:
2622: String tempScope = currentNode.getNodeName();
2623: if (tempScope.indexOf(':') != -1) {
2624: lock.scope = tempScope.substring(tempScope
2625: .indexOf(':') + 1);
2626: } else {
2627: lock.scope = tempScope;
2628: }
2629: break;
2630: }
2631: }
2632:
2633: if (lock.scope == null) {
2634: // Bad request
2635: resp.setStatus(SakaidavStatus.SC_BAD_REQUEST);
2636: }
2637:
2638: } else {
2639: // Bad request
2640: resp.setStatus(SakaidavStatus.SC_BAD_REQUEST);
2641: }
2642:
2643: if (lockTypeNode != null) {
2644:
2645: childList = lockTypeNode.getChildNodes();
2646: for (int i = 0; i < childList.getLength(); i++) {
2647: Node currentNode = childList.item(i);
2648: switch (currentNode.getNodeType()) {
2649: case Node.TEXT_NODE:
2650: break;
2651: case Node.ELEMENT_NODE:
2652: String tempType = currentNode.getNodeName();
2653: if (tempType.indexOf(':') != -1) {
2654: lock.type = tempType.substring(tempType
2655: .indexOf(':') + 1);
2656: } else {
2657: lock.type = tempType;
2658: }
2659: break;
2660: }
2661: }
2662:
2663: if (lock.type == null) {
2664: // Bad request
2665: resp.setStatus(SakaidavStatus.SC_BAD_REQUEST);
2666: }
2667:
2668: } else {
2669: // Bad request
2670: resp.setStatus(SakaidavStatus.SC_BAD_REQUEST);
2671: }
2672:
2673: if (lockOwnerNode != null) {
2674:
2675: childList = lockOwnerNode.getChildNodes();
2676: for (int i = 0; i < childList.getLength(); i++) {
2677: Node currentNode = childList.item(i);
2678: switch (currentNode.getNodeType()) {
2679: case Node.TEXT_NODE:
2680: lock.owner += currentNode.getNodeValue();
2681: break;
2682: case Node.ELEMENT_NODE:
2683: strWriter = new StringWriter();
2684: domWriter = new DOMWriter(strWriter, true);
2685: domWriter.print(currentNode);
2686: lock.owner += strWriter.toString();
2687: break;
2688: }
2689: }
2690:
2691: if (lock.owner == null) {
2692: // Bad request
2693: resp.setStatus(SakaidavStatus.SC_BAD_REQUEST);
2694: }
2695:
2696: // contribute feeds us an owner that looks
2697: // like <A:href>...</A:href>. Since we'll put it
2698: // back with a different namespace prefix, we
2699: // don't want to save it that way.
2700:
2701: lock.owner = lock.owner.replaceAll(
2702: "<(/?)[^>]+:([hH][rR][eE][fF])>", "<$1$2>");
2703: // System.out.println("lock.owner: " + lock.owner);
2704:
2705: } else {
2706: lock.owner = new String();
2707: }
2708:
2709: }
2710:
2711: String path = getRelativePath(req);
2712: String lockToken = null;
2713:
2714: lock.path = path;
2715:
2716: // Retrieve the resources
2717: // DirContext resources = getResources();
2718: DirContextSAKAI resources = getResourcesSAKAI();
2719:
2720: if (resources == null) {
2721: resp
2722: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
2723: return;
2724: }
2725:
2726: boolean exists = true;
2727: Object object = null;
2728: try {
2729: object = resources.lookup(path);
2730: } catch (NamingException e) {
2731: exists = false;
2732: }
2733:
2734: // We don't want to allow just anyone to lock a resource.
2735: // It seems reasonable to allow it only for someone who
2736: // is allowed to modify it.
2737: if (prohibited(path)
2738: || !(exists ? ContentHostingService
2739: .allowUpdateResource(adjustId(path))
2740: : ContentHostingService
2741: .allowAddResource(adjustId(path)))) {
2742: resp.sendError(SakaidavStatus.SC_FORBIDDEN, path);
2743: return;
2744: }
2745:
2746: Enumeration locksList = null;
2747:
2748: if (lockRequestType == LOCK_CREATION) {
2749:
2750: // Generating lock id
2751: String lockTokenStr = req.getServletPath() + "-"
2752: + lock.type + "-" + lock.scope + "-"
2753: + req.getUserPrincipal() + "-" + lock.depth + "-"
2754: + lock.owner + "-" + lock.tokens + "-"
2755: + lock.expiresAt + "-" + System.currentTimeMillis()
2756: + "-" + secret;
2757: lockToken = md5Encoder.encode(md5Helper.digest(lockTokenStr
2758: .getBytes()));
2759:
2760: if ((exists) && (object instanceof DirContext)
2761: && (lock.depth == INFINITY)) {
2762:
2763: // Locking a collection (and all its member resources)
2764:
2765: // Checking if a child resource of this collection is
2766: // already locked
2767: Vector lockPaths = new Vector();
2768: locksList = collectionLocks.elements();
2769: while (locksList.hasMoreElements()) {
2770: LockInfo currentLock = (LockInfo) locksList
2771: .nextElement();
2772: if (currentLock.hasExpired()) {
2773: resourceLocks.remove(currentLock.path);
2774: continue;
2775: }
2776: if ((currentLock.path.startsWith(lock.path))
2777: && ((currentLock.isExclusive()) || (lock
2778: .isExclusive()))) {
2779: // A child collection of this collection is locked
2780: lockPaths.addElement(currentLock.path);
2781: }
2782: }
2783: locksList = resourceLocks.elements();
2784: while (locksList.hasMoreElements()) {
2785: LockInfo currentLock = (LockInfo) locksList
2786: .nextElement();
2787: if (currentLock.hasExpired()) {
2788: resourceLocks.remove(currentLock.path);
2789: continue;
2790: }
2791: if ((currentLock.path.startsWith(lock.path))
2792: && ((currentLock.isExclusive()) || (lock
2793: .isExclusive()))) {
2794: // A child resource of this collection is locked
2795: lockPaths.addElement(currentLock.path);
2796: }
2797: }
2798:
2799: if (!lockPaths.isEmpty()) {
2800:
2801: // One of the child paths was locked
2802: // We generate a multistatus error report
2803:
2804: Enumeration lockPathsList = lockPaths.elements();
2805:
2806: resp.setStatus(SakaidavStatus.SC_CONFLICT);
2807:
2808: XMLWriter generatedXML = new XMLWriter();
2809: generatedXML.writeXMLHeader();
2810:
2811: generatedXML.writeElement(null, "multistatus"
2812: + generateNamespaceDeclarations(),
2813: XMLWriter.OPENING);
2814:
2815: while (lockPathsList.hasMoreElements()) {
2816: generatedXML.writeElement(null, "response",
2817: XMLWriter.OPENING);
2818: generatedXML.writeElement(null, "href",
2819: XMLWriter.OPENING);
2820: generatedXML.writeText((String) lockPathsList
2821: .nextElement());
2822: generatedXML.writeElement(null, "href",
2823: XMLWriter.CLOSING);
2824: generatedXML.writeElement(null, "status",
2825: XMLWriter.OPENING);
2826: generatedXML
2827: .writeText("HTTP/1.1 "
2828: + SakaidavStatus.SC_LOCKED
2829: + " "
2830: + SakaidavStatus
2831: .getStatusText(SakaidavStatus.SC_LOCKED));
2832: generatedXML.writeElement(null, "status",
2833: XMLWriter.CLOSING);
2834:
2835: generatedXML.writeElement(null, "response",
2836: XMLWriter.CLOSING);
2837: }
2838:
2839: generatedXML.writeElement(null, "multistatus",
2840: XMLWriter.CLOSING);
2841:
2842: Writer writer = resp.getWriter();
2843: writer.write(generatedXML.toString());
2844: writer.close();
2845:
2846: return;
2847:
2848: }
2849:
2850: boolean addLock = true;
2851:
2852: // Checking if there is already a shared lock on this path
2853: locksList = collectionLocks.elements();
2854: while (locksList.hasMoreElements()) {
2855:
2856: LockInfo currentLock = (LockInfo) locksList
2857: .nextElement();
2858: if (currentLock.path.equals(lock.path)) {
2859:
2860: if (currentLock.isExclusive()) {
2861: resp.sendError(SakaidavStatus.SC_LOCKED);
2862: return;
2863: } else {
2864: if (lock.isExclusive()) {
2865: resp
2866: .sendError(SakaidavStatus.SC_LOCKED);
2867: return;
2868: }
2869: }
2870:
2871: currentLock.tokens.addElement(lockToken);
2872: lock = currentLock;
2873: addLock = false;
2874:
2875: }
2876:
2877: }
2878:
2879: if (addLock) {
2880: lock.tokens.addElement(lockToken);
2881: collectionLocks.addElement(lock);
2882: }
2883:
2884: } else {
2885:
2886: // Locking a single resource
2887:
2888: // Retrieving an already existing lock on that resource
2889: LockInfo presentLock = (LockInfo) resourceLocks
2890: .get(lock.path);
2891: if (presentLock != null) {
2892:
2893: if ((presentLock.isExclusive())
2894: || (lock.isExclusive())) {
2895: // If either lock is exclusive, the lock can't be
2896: // granted
2897: resp
2898: .sendError(SakaidavStatus.SC_PRECONDITION_FAILED);
2899: return;
2900: } else {
2901: presentLock.tokens.addElement(lockToken);
2902: lock = presentLock;
2903: }
2904:
2905: } else {
2906:
2907: lock.tokens.addElement(lockToken);
2908: resourceLocks.put(lock.path, lock);
2909:
2910: // Checking if a resource exists at this path
2911: exists = true;
2912: try {
2913: object = resources.lookup(path);
2914: } catch (NamingException e) {
2915: exists = false;
2916: }
2917: if (!exists) {
2918:
2919: // "Creating" a lock-null resource
2920: int slash = lock.path.lastIndexOf('/');
2921: String parentPath = lock.path.substring(0,
2922: slash);
2923:
2924: Vector lockNulls = (Vector) lockNullResources
2925: .get(parentPath);
2926: if (lockNulls == null) {
2927: lockNulls = new Vector();
2928: lockNullResources
2929: .put(parentPath, lockNulls);
2930: }
2931:
2932: lockNulls.addElement(lock.path);
2933:
2934: }
2935:
2936: }
2937:
2938: }
2939:
2940: }
2941:
2942: if (lockRequestType == LOCK_REFRESH) {
2943:
2944: String ifHeader = req.getHeader("If");
2945: if (ifHeader == null)
2946: ifHeader = "";
2947:
2948: // Checking resource locks
2949:
2950: LockInfo toRenew = (LockInfo) resourceLocks.get(path);
2951: Enumeration tokenList = null;
2952: if (lock != null) {
2953:
2954: // At least one of the tokens of the locks must have been given
2955:
2956: tokenList = toRenew.tokens.elements();
2957: while (tokenList.hasMoreElements()) {
2958: String token = (String) tokenList.nextElement();
2959: if (ifHeader.indexOf(token) != -1) {
2960: toRenew.expiresAt = lock.expiresAt;
2961: lock = toRenew;
2962: }
2963: }
2964:
2965: }
2966:
2967: // Checking inheritable collection locks
2968:
2969: Enumeration collectionLocksList = collectionLocks
2970: .elements();
2971: while (collectionLocksList.hasMoreElements()) {
2972: toRenew = (LockInfo) collectionLocksList.nextElement();
2973: if (path.equals(toRenew.path)) {
2974:
2975: tokenList = toRenew.tokens.elements();
2976: while (tokenList.hasMoreElements()) {
2977: String token = (String) tokenList.nextElement();
2978: if (ifHeader.indexOf(token) != -1) {
2979: toRenew.expiresAt = lock.expiresAt;
2980: lock = toRenew;
2981: }
2982: }
2983:
2984: }
2985: }
2986:
2987: }
2988:
2989: // Set the status, then generate the XML response containing
2990: // the lock information
2991: XMLWriter generatedXML = new XMLWriter();
2992: generatedXML.writeXMLHeader();
2993: generatedXML.writeElement(null, "prop"
2994: + generateNamespaceDeclarations(), XMLWriter.OPENING);
2995:
2996: generatedXML.writeElement(null, "lockdiscovery",
2997: XMLWriter.OPENING);
2998:
2999: lock.toXML(generatedXML, true);
3000:
3001: generatedXML.writeElement(null, "lockdiscovery",
3002: XMLWriter.CLOSING);
3003:
3004: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
3005:
3006: /* the RFC requires this header in response to lock creation */
3007:
3008: if (lockRequestType == LOCK_CREATION)
3009: resp
3010: .addHeader("Lock-Token", "opaquelocktoken:"
3011: + lockToken);
3012: resp.setStatus(SakaidavStatus.SC_OK);
3013: resp.setContentType("text/xml; charset=UTF-8");
3014: Writer writer = resp.getWriter();
3015: writer.write(generatedXML.toString());
3016: writer.close();
3017:
3018: }
3019:
3020: /**
3021: * UNLOCK Method.
3022: */
3023: protected void doUnlock(HttpServletRequest req,
3024: HttpServletResponse resp) throws ServletException,
3025: IOException {
3026:
3027: if (readOnly) {
3028: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
3029: return;
3030: }
3031:
3032: if (isLocked(req)) {
3033: resp.sendError(SakaidavStatus.SC_LOCKED);
3034: return;
3035: }
3036:
3037: String path = getRelativePath(req);
3038:
3039: String lockTokenHeader = req.getHeader("Lock-Token");
3040: if (lockTokenHeader == null)
3041: lockTokenHeader = "";
3042:
3043: // Only allow lock/unlock for someone who can do update
3044: // NB: we don't check that the person unlocking is the same as
3045: // the one locking. Experience with Contribute says that
3046: // significant size groups have a big problem with people
3047: // leaving resources inadvertently locked. No one will use
3048: // the system if they continually have to find a privileged
3049: // user to unscramble things. Contribute does check who owns
3050: // the lock, so to bypass the lock you have to run a copy of
3051: // DAVExplorer and unlock it manually. That seems like a
3052: // good compromise. At any rate, there needs to be some
3053: // check here, which there wasn't originally.
3054:
3055: // when creating, we know whether the resource exists, so we
3056: // can check add or update appropriately. Here we don't, and
3057: // I think it's faster just to do both checks. In fact
3058: // I believe allowAddResource doesn't currently check whether
3059: // the resource exists, so it would be safe to use alone, but
3060: // that seems like a bad assumption to make.
3061: if (prohibited(path)
3062: || !(ContentHostingService
3063: .allowAddResource(adjustId(path)) || ContentHostingService
3064: .allowUpdateResource(adjustId(path))))
3065:
3066: {
3067: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
3068: return;
3069: }
3070:
3071: // Checking resource locks
3072:
3073: LockInfo lock = (LockInfo) resourceLocks.get(path);
3074: Enumeration tokenList = null;
3075: if (lock != null) {
3076:
3077: // At least one of the tokens of the locks must have been given
3078:
3079: tokenList = lock.tokens.elements();
3080: while (tokenList.hasMoreElements()) {
3081: String token = (String) tokenList.nextElement();
3082: if (lockTokenHeader.indexOf(token) != -1) {
3083: lock.tokens.removeElement(token);
3084: }
3085: }
3086:
3087: if (lock.tokens.isEmpty()) {
3088: resourceLocks.remove(path);
3089: // Removing any lock-null resource which would be present
3090: lockNullResources.remove(path);
3091: }
3092:
3093: }
3094:
3095: // Checking inheritable collection locks
3096:
3097: Enumeration collectionLocksList = collectionLocks.elements();
3098: while (collectionLocksList.hasMoreElements()) {
3099: lock = (LockInfo) collectionLocksList.nextElement();
3100: if (path.equals(lock.path)) {
3101:
3102: tokenList = lock.tokens.elements();
3103: while (tokenList.hasMoreElements()) {
3104: String token = (String) tokenList.nextElement();
3105: if (lockTokenHeader.indexOf(token) != -1) {
3106: lock.tokens.removeElement(token);
3107: break;
3108: }
3109: }
3110:
3111: if (lock.tokens.isEmpty()) {
3112: collectionLocks.removeElement(lock);
3113: // Removing any lock-null resource which would be present
3114: lockNullResources.remove(path);
3115: }
3116:
3117: }
3118: }
3119:
3120: resp.setStatus(SakaidavStatus.SC_NO_CONTENT);
3121:
3122: }
3123:
3124: // -------------------------------------------------------- Private Methods
3125:
3126: /**
3127: * Generate the namespace declarations.
3128: */
3129: private String generateNamespaceDeclarations() {
3130: return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
3131: }
3132:
3133: /**
3134: * Check to see if a resource is currently write locked. The method will look at the "If" header to make sure the client has give the appropriate lock tokens.
3135: *
3136: * @param req
3137: * Servlet request
3138: * @return boolean true if the resource is locked (and no appropriate lock token has been found for at least one of the non-shared locks which are present on the resource).
3139: */
3140: private boolean isLocked(HttpServletRequest req) {
3141:
3142: String path = getRelativePath(req);
3143:
3144: String ifHeader = req.getHeader("If");
3145: if (ifHeader == null)
3146: ifHeader = "";
3147:
3148: String lockTokenHeader = req.getHeader("Lock-Token");
3149: if (lockTokenHeader == null)
3150: lockTokenHeader = "";
3151:
3152: return isLocked(path, ifHeader + lockTokenHeader);
3153:
3154: }
3155:
3156: /**
3157: * Check to see if a resource is currently write locked.
3158: *
3159: * @param path
3160: * Path of the resource
3161: * @param ifHeader
3162: * "If" HTTP header which was included in the request
3163: * @return boolean true if the resource is locked (and no appropriate lock token has been found for at least one of the non-shared locks which are present on the resource).
3164: */
3165: private boolean isLocked(String path, String ifHeader) {
3166:
3167: // Checking resource locks
3168:
3169: LockInfo lock = (LockInfo) resourceLocks.get(path);
3170: Enumeration tokenList = null;
3171: if ((lock != null) && (lock.hasExpired())) {
3172: resourceLocks.remove(path);
3173: } else if (lock != null) {
3174:
3175: // At least one of the tokens of the locks must have been given
3176:
3177: tokenList = lock.tokens.elements();
3178: boolean tokenMatch = false;
3179: while (tokenList.hasMoreElements()) {
3180: String token = (String) tokenList.nextElement();
3181: if (ifHeader.indexOf(token) != -1)
3182: tokenMatch = true;
3183: }
3184: if (!tokenMatch)
3185: return true;
3186:
3187: }
3188:
3189: // Checking inheritable collection locks
3190:
3191: Enumeration collectionLocksList = collectionLocks.elements();
3192: while (collectionLocksList.hasMoreElements()) {
3193: lock = (LockInfo) collectionLocksList.nextElement();
3194: if (lock.hasExpired()) {
3195: collectionLocks.removeElement(lock);
3196: } else if (path.startsWith(lock.path)) {
3197:
3198: tokenList = lock.tokens.elements();
3199: boolean tokenMatch = false;
3200: while (tokenList.hasMoreElements()) {
3201: String token = (String) tokenList.nextElement();
3202: if (ifHeader.indexOf(token) != -1)
3203: tokenMatch = true;
3204: }
3205: if (!tokenMatch)
3206: return true;
3207:
3208: }
3209: }
3210:
3211: return false;
3212:
3213: }
3214:
3215: /**
3216: * Get the destination path from the header
3217: */
3218:
3219: private String getDestinationPath(HttpServletRequest req) {
3220: // Parsing destination header
3221:
3222: String destinationPath = req.getHeader("Destination");
3223:
3224: if (destinationPath == null) {
3225: return null;
3226: }
3227:
3228: int protocolIndex = destinationPath.indexOf("://");
3229: if (protocolIndex >= 0) {
3230: // if the Destination URL contains the protocol, we can safely
3231: // trim everything upto the first "/" character after "://"
3232: int firstSeparator = destinationPath.indexOf("/",
3233: protocolIndex + 4);
3234: if (firstSeparator < 0) {
3235: destinationPath = "/";
3236: } else {
3237: destinationPath = destinationPath
3238: .substring(firstSeparator);
3239: }
3240: } else {
3241: String hostName = req.getServerName();
3242: if ((hostName != null)
3243: && (destinationPath.startsWith(hostName))) {
3244: destinationPath = destinationPath.substring(hostName
3245: .length());
3246: }
3247:
3248: int portIndex = destinationPath.indexOf(":");
3249: if (portIndex >= 0) {
3250: destinationPath = destinationPath.substring(portIndex);
3251: }
3252:
3253: if (destinationPath.startsWith(":")) {
3254: int firstSeparator = destinationPath.indexOf("/");
3255: if (firstSeparator < 0) {
3256: destinationPath = "/";
3257: } else {
3258: destinationPath = destinationPath
3259: .substring(firstSeparator);
3260: }
3261: }
3262: }
3263:
3264: String contextPath = req.getContextPath();
3265: if ((contextPath != null)
3266: && (destinationPath.startsWith(contextPath))) {
3267: destinationPath = destinationPath.substring(contextPath
3268: .length());
3269: }
3270:
3271: String pathInfo = req.getPathInfo();
3272: if (pathInfo != null) {
3273: String servletPath = req.getServletPath();
3274: if ((servletPath != null)
3275: && (destinationPath.startsWith(servletPath))) {
3276: destinationPath = destinationPath.substring(servletPath
3277: .length());
3278: }
3279: }
3280:
3281: destinationPath = RequestUtil.URLDecode(
3282: normalize(destinationPath), "UTF8");
3283:
3284: return destinationPath;
3285:
3286: } // getDestinationPath
3287:
3288: private ResourcePropertiesEdit duplicateResourceProperties(
3289: ResourceProperties properties, String id) {
3290:
3291: ResourcePropertiesEdit resourceProperties = ContentHostingService
3292: .newResourceProperties();
3293: try {
3294:
3295: if (properties == null)
3296: return resourceProperties;
3297:
3298: // loop throuh the properties
3299: Iterator propertyNames = properties.getPropertyNames();
3300: while (propertyNames.hasNext()) {
3301: String propertyName = (String) propertyNames.next();
3302: if (!propertyName
3303: .equals(ResourceProperties.PROP_DISPLAY_NAME))
3304: resourceProperties.addProperty(propertyName,
3305: properties.getProperty(propertyName));
3306: }
3307:
3308: } catch (Exception e) {
3309: return resourceProperties;
3310: }
3311:
3312: return resourceProperties;
3313:
3314: } // duplicateResourceProperties
3315:
3316: // better than before, but rather than copyIntoFolder we really need to write our own recursive
3317: // code. There are two problems; (1) copyIntoFolder will add .bin when there is no extension (2) it
3318: // doesn't check for "/protected" The current code is the minimum necessary to support OS X.
3319:
3320: private void copyCollection(String id, String new_id)
3321: throws IdUnusedException, PermissionException,
3322: TypeException, IdUnusedException, IdLengthException,
3323: IdUsedException, IdUniquenessException, IdInvalidException,
3324: InUseException, InconsistentException, OverQuotaException,
3325: ServerOverloadException {
3326:
3327: if (!id.endsWith("/"))
3328: id = id + "/";
3329:
3330: if (!new_id.endsWith("/"))
3331: new_id = new_id + "/";
3332:
3333: ContentCollection this Collection = ContentHostingService
3334: .getCollection(id);
3335:
3336: List members = this Collection.getMembers();
3337:
3338: ResourceProperties properties = this Collection.getProperties();
3339: ResourcePropertiesEdit newProps = duplicateResourceProperties(
3340: properties, this Collection.getId());
3341:
3342: String name = new_id;
3343: if (name.endsWith("/"))
3344: name = name.substring(0, name.length() - 1);
3345: int i = name.lastIndexOf("/");
3346: if (i >= 0)
3347: name = name.substring(i + 1);
3348: newProps
3349: .addProperty(ResourceProperties.PROP_DISPLAY_NAME, name);
3350:
3351: ContentCollection newCollection = ContentHostingService
3352: .addCollection(new_id, newProps);
3353:
3354: Iterator memberIt = members.iterator();
3355: while (memberIt.hasNext()) {
3356: String member_id = (String) memberIt.next();
3357:
3358: // this isn't perfect. It only protects the top two levels of directory
3359: if (!(doProtected
3360: && member_id.toLowerCase().indexOf("/protected") >= 0 && (!ContentHostingService
3361: .allowAddCollection(adjustId(member_id)))))
3362: ContentHostingService.copyIntoFolder(member_id, new_id);
3363: }
3364:
3365: }
3366:
3367: /**
3368: * Copy a resource.
3369: *
3370: * @param req
3371: * Servlet request
3372: * @param resp
3373: * Servlet response
3374: * @return boolean true if the copy is successful
3375: */
3376: private boolean copyResource(HttpServletRequest req,
3377: HttpServletResponse resp) throws ServletException,
3378: IOException {
3379:
3380: String destinationPath = getDestinationPath(req);
3381:
3382: if (destinationPath == null) {
3383: resp.sendError(SakaidavStatus.SC_BAD_REQUEST);
3384: return false;
3385: }
3386:
3387: if (debug > 0)
3388: if (M_log.isDebugEnabled())
3389: M_log.debug("Dest path :" + destinationPath);
3390:
3391: if ((destinationPath.toUpperCase().startsWith("/WEB-INF"))
3392: || (destinationPath.toUpperCase()
3393: .startsWith("/META-INF"))) {
3394: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
3395: return false;
3396: }
3397:
3398: String path = getRelativePath(req);
3399:
3400: if (prohibited(path)
3401: || (path.toUpperCase().startsWith("/WEB-INF"))
3402: || (path.toUpperCase().startsWith("/META-INF"))) {
3403: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
3404: return false;
3405: }
3406:
3407: if (prohibited(destinationPath) || destinationPath.equals(path)) {
3408: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
3409: return false;
3410: }
3411:
3412: // Parsing overwrite header
3413:
3414: boolean overwrite = true;
3415: String overwriteHeader = req.getHeader("Overwrite");
3416:
3417: if (overwriteHeader != null) {
3418: if (overwriteHeader.equalsIgnoreCase("T")) {
3419: overwrite = true;
3420: } else {
3421: overwrite = false;
3422: }
3423: }
3424:
3425: // Overwriting the destination
3426:
3427: // Retrieve the resources
3428: // DirContext resources = getResources();
3429: DirContextSAKAI resources = getResourcesSAKAI();
3430:
3431: if (resources == null) {
3432: resp
3433: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
3434: return false;
3435: }
3436:
3437: boolean exists = true;
3438: try {
3439: resources.lookup(destinationPath);
3440: } catch (NamingException e) {
3441: exists = false;
3442: }
3443:
3444: if (overwrite) {
3445:
3446: // Delete destination resource, if it exists
3447: if (exists) {
3448: if (!deleteResource(destinationPath, req, resp)) {
3449: return false;
3450: } else {
3451: resp.setStatus(SakaidavStatus.SC_NO_CONTENT);
3452: }
3453: } else {
3454: resp.setStatus(SakaidavStatus.SC_CREATED);
3455: }
3456:
3457: } else {
3458:
3459: // If the destination exists, then it's a conflict
3460: if (exists) {
3461: resp.sendError(SakaidavStatus.SC_PRECONDITION_FAILED);
3462: return false;
3463: }
3464:
3465: }
3466:
3467: // Copying source to destination
3468:
3469: Hashtable errorList = new Hashtable();
3470:
3471: boolean result = copyResource(resources, errorList, path,
3472: destinationPath);
3473:
3474: if ((!result) || (!errorList.isEmpty())) {
3475:
3476: sendReport(req, resp, errorList);
3477: return false;
3478:
3479: }
3480:
3481: // Removing any lock-null resource which would be present at
3482: // the destination path
3483: lockNullResources.remove(destinationPath);
3484:
3485: return true;
3486:
3487: }
3488:
3489: /**
3490: * Copy a collection.
3491: *
3492: * @param resources
3493: * Resources implementation to be used
3494: * @param errorList
3495: * Hashtable containing the list of errors which occurred during the copy operation
3496: * @param source
3497: * Path of the resource to be copied
3498: * @param dest
3499: * Destination path
3500: */
3501: private boolean copyResource(DirContextSAKAI resources,
3502: Hashtable errorList, String source, String dest) {
3503:
3504: if (debug > 1)
3505: if (M_log.isDebugEnabled())
3506: M_log.debug("Copy: " + source + " To: " + dest);
3507:
3508: source = fixDirPathSAKAI(source);
3509: dest = fixDirPathSAKAI(dest);
3510:
3511: // System.out.println("copyResource source="+source+" dest="+dest);
3512:
3513: if (prohibited(source) || prohibited(dest)) {
3514: errorList.put(source, new Integer(
3515: SakaidavStatus.SC_FORBIDDEN));
3516: return false;
3517: }
3518:
3519: source = adjustId(source);
3520: dest = adjustId(dest);
3521:
3522: try {
3523: boolean isCollection = ContentHostingService.getProperties(
3524: source).getBooleanProperty(
3525: ResourceProperties.PROP_IS_COLLECTION);
3526: String destfolder = null;
3527: String tempname = null;
3528:
3529: if (isCollection)
3530: copyCollection(adjustId(source), adjustId(dest));
3531: else
3532: ContentHostingService.copy(adjustId(source),
3533: adjustId(dest));
3534: } catch (EntityPropertyNotDefinedException e) {
3535: // System.out.println("propnotdef " + e);
3536: errorList.put(source, new Integer(
3537: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3538: return false;
3539: } catch (EntityPropertyTypeException e) {
3540: // System.out.println("propntype " + e);
3541: errorList.put(source, new Integer(
3542: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3543: return false;
3544: } catch (IdUsedException e)
3545: // internal error because caller checked for this
3546: {
3547: // System.out.println("idunused " + e);
3548: errorList.put(source, new Integer(
3549: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3550: return false;
3551: } catch (IdUniquenessException e) {
3552: // System.out.println("iduniqu " + e);
3553: errorList.put(source, new Integer(
3554: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3555: return false;
3556: } catch (IdLengthException e) {
3557: // System.out.println("idlen " + e);
3558: errorList.put(source, new Integer(
3559: SakaidavStatus.SC_FORBIDDEN));
3560: return false;
3561: } catch (InconsistentException e) {
3562: // System.out.println("inconsis " + e);
3563: errorList.put(source, new Integer(
3564: SakaidavStatus.SC_CONFLICT));
3565: return false;
3566: } catch (PermissionException e) {
3567: // System.out.println("perm " + e);
3568: errorList.put(source, new Integer(
3569: SakaidavStatus.SC_FORBIDDEN));
3570: return false;
3571: } catch (InUseException e) {
3572: // System.out.println("in use " + e);
3573: errorList.put(source, new Integer(
3574: SakaidavStatus.SC_CONFLICT));
3575: return false;
3576: } catch (IdUnusedException e) {
3577: // System.out.println("unused " + e);
3578: errorList.put(source, new Integer(
3579: SakaidavStatus.SC_NOT_FOUND));
3580: return false;
3581: } catch (OverQuotaException e) {
3582: // System.out.println("quota " + e);
3583: errorList.put(source, new Integer(
3584: SakaidavStatus.SC_FORBIDDEN));
3585: return false;
3586: } catch (IdInvalidException e) {
3587: // System.out.println("quota " + e);
3588: errorList.put(source, new Integer(
3589: SakaidavStatus.SC_FORBIDDEN));
3590: return false;
3591: } catch (TypeException e) {
3592: // System.out.println("type " + e);
3593: errorList.put(source, new Integer(
3594: SakaidavStatus.SC_FORBIDDEN));
3595: return false;
3596: } catch (ServerOverloadException e) {
3597: // System.out.println("overload " + e);
3598: errorList.put(source, new Integer(
3599: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3600: return false;
3601: }
3602:
3603: // We did not have an error
3604: errorList.clear();
3605: return true;
3606:
3607: }
3608:
3609: /**
3610: * Delete a resource.
3611: *
3612: * @param req
3613: * Servlet request
3614: * @param resp
3615: * Servlet response
3616: * @return boolean true if the copy is successful
3617: */
3618: private boolean deleteResource(HttpServletRequest req,
3619: HttpServletResponse resp) throws ServletException,
3620: IOException {
3621:
3622: String path = getRelativePathSAKAI(req);
3623:
3624: return deleteResource(path, req, resp);
3625:
3626: }
3627:
3628: /**
3629: * Delete a resource.
3630: *
3631: * @param path
3632: * Path of the resource which is to be deleted
3633: * @param req
3634: * Servlet request
3635: * @param resp
3636: * Servlet response
3637: */
3638: private boolean deleteResource(String path, HttpServletRequest req,
3639: HttpServletResponse resp) throws ServletException,
3640: IOException {
3641:
3642: if (prohibited(path)
3643: || (path.toUpperCase().startsWith("/WEB-INF"))
3644: || (path.toUpperCase().startsWith("/META-INF"))) {
3645: resp.sendError(SakaidavStatus.SC_FORBIDDEN);
3646: return false;
3647: }
3648:
3649: String ifHeader = req.getHeader("If");
3650: if (ifHeader == null)
3651: ifHeader = "";
3652:
3653: String lockTokenHeader = req.getHeader("Lock-Token");
3654: if (lockTokenHeader == null)
3655: lockTokenHeader = "";
3656:
3657: if (isLocked(path, ifHeader + lockTokenHeader)) {
3658: resp.sendError(SakaidavStatus.SC_LOCKED);
3659: return false;
3660: }
3661:
3662: path = fixDirPathSAKAI(path); // In case we are a directory
3663:
3664: boolean isCollection = false;
3665: try {
3666: isCollection = ContentHostingService.getProperties(
3667: adjustId(path)).getBooleanProperty(
3668: ResourceProperties.PROP_IS_COLLECTION);
3669:
3670: if (isCollection) {
3671: ContentHostingService.removeCollection(adjustId(path));
3672: } else {
3673: ContentHostingService.removeResource(adjustId(path));
3674: }
3675: } catch (PermissionException e) {
3676: return false;
3677: } catch (InUseException e) {
3678: return false;
3679: } catch (IdUnusedException e) {
3680: // Resource not found (this is actually the normal case)
3681: } catch (EntityPropertyNotDefinedException e) {
3682: M_log
3683: .warn("SAKAIDavServlet.deleteResource() - EntityPropertyNotDefinedException "
3684: + path);
3685: return false;
3686: } catch (EntityPropertyTypeException e) {
3687: M_log
3688: .warn("SAKAIDavServlet.deleteResource() - EntityPropertyTypeException "
3689: + path);
3690: return false;
3691: } catch (TypeException e) {
3692: M_log
3693: .warn("SAKAIDavServlet.deleteResource() - TypeException "
3694: + path);
3695: return false;
3696: } catch (ServerOverloadException e) {
3697: M_log
3698: .warn("SAKAIDavServlet.deleteResource() - ServerOverloadException "
3699: + path);
3700: return false;
3701: }
3702: return true;
3703:
3704: }
3705:
3706: /**
3707: * Deletes a collection.
3708: *
3709: * @param resources
3710: * Resources implementation associated with the context
3711: * @param path
3712: * Path to the collection to be deleted
3713: * @param errorList
3714: * Contains the list of the errors which occurred
3715: */
3716: private void deleteCollection(HttpServletRequest req,
3717: DirContext resources, String path, Hashtable errorList) {
3718:
3719: if (debug > 1)
3720: if (M_log.isDebugEnabled())
3721: M_log.debug("deleteCollection:" + path);
3722:
3723: if (prohibited(path)
3724: || (path.toUpperCase().startsWith("/WEB-INF"))
3725: || (path.toUpperCase().startsWith("/META-INF"))) {
3726: errorList.put(path,
3727: new Integer(SakaidavStatus.SC_FORBIDDEN));
3728: return;
3729: }
3730:
3731: String ifHeader = req.getHeader("If");
3732: if (ifHeader == null)
3733: ifHeader = "";
3734:
3735: String lockTokenHeader = req.getHeader("Lock-Token");
3736: if (lockTokenHeader == null)
3737: lockTokenHeader = "";
3738:
3739: Enumeration enumer = null;
3740: try {
3741: enumer = resources.list(path);
3742: } catch (NamingException e) {
3743: errorList.put(path, new Integer(
3744: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3745: return;
3746: }
3747:
3748: while (enumer.hasMoreElements()) {
3749: NameClassPair ncPair = (NameClassPair) enumer.nextElement();
3750: String childName = path;
3751: if (!childName.equals("/"))
3752: childName += "/";
3753: childName += ncPair.getName();
3754:
3755: if (isLocked(childName, ifHeader + lockTokenHeader)) {
3756:
3757: errorList.put(childName, new Integer(
3758: SakaidavStatus.SC_LOCKED));
3759:
3760: } else {
3761:
3762: try {
3763: Object object = resources.lookup(childName);
3764: if (object instanceof DirContext) {
3765: deleteCollection(req, resources, childName,
3766: errorList);
3767: }
3768:
3769: try {
3770: resources.unbind(childName);
3771: } catch (NamingException e) {
3772: if (!(object instanceof DirContext)) {
3773: // If it's not a collection, then it's an unknown
3774: // error
3775: errorList
3776: .put(
3777: childName,
3778: new Integer(
3779: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3780: }
3781: }
3782: } catch (NamingException e) {
3783: errorList.put(childName, new Integer(
3784: SakaidavStatus.SC_INTERNAL_SERVER_ERROR));
3785: }
3786: }
3787:
3788: }
3789:
3790: }
3791:
3792: /**
3793: * Send a multistatus element containing a complete error report to the client.
3794: *
3795: * @param req
3796: * Servlet request
3797: * @param resp
3798: * Servlet response
3799: * @param errorList
3800: * List of error to be displayed
3801: */
3802: private void sendReport(HttpServletRequest req,
3803: HttpServletResponse resp, Hashtable errorList)
3804: throws ServletException, IOException {
3805:
3806: resp.setStatus(SakaidavStatus.SC_MULTI_STATUS);
3807:
3808: String absoluteUri = req.getRequestURI();
3809: String relativePath = getRelativePath(req);
3810:
3811: XMLWriter generatedXML = new XMLWriter();
3812: generatedXML.writeXMLHeader();
3813:
3814: generatedXML.writeElement(null, "multistatus"
3815: + generateNamespaceDeclarations(), XMLWriter.OPENING);
3816:
3817: Enumeration pathList = errorList.keys();
3818: while (pathList.hasMoreElements()) {
3819:
3820: String errorPath = (String) pathList.nextElement();
3821: int errorCode = ((Integer) errorList.get(errorPath))
3822: .intValue();
3823:
3824: generatedXML.writeElement(null, "response",
3825: XMLWriter.OPENING);
3826:
3827: generatedXML.writeElement(null, "href", XMLWriter.OPENING);
3828: String toAppend = errorPath
3829: .substring(relativePath.length());
3830: if (!toAppend.startsWith("/"))
3831: toAppend = "/" + toAppend;
3832: generatedXML.writeText(absoluteUri + toAppend);
3833: generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
3834: generatedXML
3835: .writeElement(null, "status", XMLWriter.OPENING);
3836: generatedXML.writeText("HTTP/1.1 " + errorCode + " "
3837: + SakaidavStatus.getStatusText(errorCode));
3838: generatedXML
3839: .writeElement(null, "status", XMLWriter.CLOSING);
3840:
3841: generatedXML.writeElement(null, "response",
3842: XMLWriter.CLOSING);
3843:
3844: }
3845:
3846: generatedXML.writeElement(null, "multistatus",
3847: XMLWriter.CLOSING);
3848:
3849: Writer writer = resp.getWriter();
3850: writer.write(generatedXML.toString());
3851: writer.close();
3852:
3853: }
3854:
3855: /**
3856: * Propfind helper method.
3857: *
3858: * @param resources
3859: * Resources object associated with this context
3860: * @param generatedXML
3861: * XML response to the Propfind request
3862: * @param path
3863: * Path of the current resource
3864: * @param type
3865: * Propfind type
3866: * @param propertiesVector
3867: * If the propfind type is find properties by name, then this Vector contains those properties
3868: */
3869: private void parseProperties(HttpServletRequest req,
3870: DirContextSAKAI resources, XMLWriter generatedXML,
3871: String path, int type, Vector propertiesVector) {
3872: // Exclude any resource in the /WEB-INF and /META-INF subdirectories
3873: // (the "toUpperCase()" avoids problems on Windows systems)
3874: if (path.toUpperCase().startsWith("/WEB-INF")
3875: || path.toUpperCase().startsWith("/META-INF"))
3876: return;
3877:
3878: ResourceInfoSAKAI resourceInfo = new ResourceInfoSAKAI(path,
3879: resources);
3880:
3881: generatedXML.writeElement(null, "response", XMLWriter.OPENING);
3882: String status = new String("HTTP/1.1 " + SakaidavStatus.SC_OK
3883: + " "
3884: + SakaidavStatus.getStatusText(SakaidavStatus.SC_OK));
3885:
3886: // Generating href element
3887: generatedXML.writeElement(null, "href", XMLWriter.OPENING);
3888:
3889: String href = (String) req
3890: .getAttribute("javax.servlet.forward.servlet_path");
3891: if (href == null) {
3892: href = (String) req
3893: .getAttribute("javax.servlet.include.servlet_path");
3894: }
3895: if (href == null) {
3896: href = req.getContextPath();
3897: }
3898:
3899: if ((href.endsWith("/")) && (path.startsWith("/")))
3900: href += path.substring(1);
3901: else
3902: href += path;
3903: if ((resourceInfo.collection) && (!href.endsWith("/")))
3904: href += "/";
3905:
3906: if (M_log.isDebugEnabled())
3907: M_log.debug("parserProperties href=" + href);
3908:
3909: generatedXML.writeText(rewriteUrl(href));
3910:
3911: generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
3912:
3913: String resourceName = justName(path);
3914:
3915: switch (type) {
3916:
3917: case FIND_ALL_PROP:
3918:
3919: generatedXML.writeElement(null, "propstat",
3920: XMLWriter.OPENING);
3921: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
3922:
3923: generatedXML.writeProperty(null, "creationdate",
3924: getISOCreationDate(resourceInfo.creationDate));
3925: generatedXML.writeElement(null, "displayname",
3926: XMLWriter.OPENING);
3927: generatedXML.writeData(resourceName);
3928: generatedXML.writeElement(null, "displayname",
3929: XMLWriter.CLOSING);
3930: generatedXML.writeProperty(null, "getcontentlanguage",
3931: Locale.getDefault().toString());
3932: if (!resourceInfo.collection) {
3933: generatedXML.writeProperty(null, "getlastmodified",
3934: resourceInfo.httpDate);
3935: generatedXML.writeProperty(null, "getcontentlength",
3936: String.valueOf(resourceInfo.length));
3937: generatedXML.writeProperty(null, "getcontenttype",
3938: resourceInfo.MIMEType);
3939: // getServletContext().getMimeType(resourceInfo.path));
3940: generatedXML.writeProperty(null, "getetag",
3941: resourceInfo.eTag);
3942: // getETagValue(resourceInfo, true));
3943: generatedXML.writeElement(null, "resourcetype",
3944: XMLWriter.NO_CONTENT);
3945: } else {
3946: generatedXML.writeElement(null, "resourcetype",
3947: XMLWriter.OPENING);
3948: generatedXML.writeElement(null, "collection",
3949: XMLWriter.NO_CONTENT);
3950: generatedXML.writeElement(null, "resourcetype",
3951: XMLWriter.CLOSING);
3952: }
3953:
3954: generatedXML.writeProperty(null, "source", "");
3955:
3956: String supportedLocks = "<lockentry>"
3957: + "<lockscope><exclusive/></lockscope>"
3958: + "<locktype><write/></locktype>" + "</lockentry>"
3959: + "<lockentry>"
3960: + "<lockscope><shared/></lockscope>"
3961: + "<locktype><write/></locktype>" + "</lockentry>";
3962: generatedXML.writeElement(null, "supportedlock",
3963: XMLWriter.OPENING);
3964: generatedXML.writeText(supportedLocks);
3965: generatedXML.writeElement(null, "supportedlock",
3966: XMLWriter.CLOSING);
3967:
3968: generateLockDiscovery(path, generatedXML);
3969:
3970: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
3971: generatedXML
3972: .writeElement(null, "status", XMLWriter.OPENING);
3973: generatedXML.writeText(status);
3974: generatedXML
3975: .writeElement(null, "status", XMLWriter.CLOSING);
3976: generatedXML.writeElement(null, "propstat",
3977: XMLWriter.CLOSING);
3978:
3979: break;
3980:
3981: case FIND_PROPERTY_NAMES:
3982:
3983: generatedXML.writeElement(null, "propstat",
3984: XMLWriter.OPENING);
3985: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
3986:
3987: generatedXML.writeElement(null, "creationdate",
3988: XMLWriter.NO_CONTENT);
3989: generatedXML.writeElement(null, "displayname",
3990: XMLWriter.NO_CONTENT);
3991: if (!resourceInfo.collection) {
3992: generatedXML.writeElement(null, "getcontentlanguage",
3993: XMLWriter.NO_CONTENT);
3994: generatedXML.writeElement(null, "getcontentlength",
3995: XMLWriter.NO_CONTENT);
3996: generatedXML.writeElement(null, "getcontenttype",
3997: XMLWriter.NO_CONTENT);
3998: generatedXML.writeElement(null, "getetag",
3999: XMLWriter.NO_CONTENT);
4000: generatedXML.writeElement(null, "getlastmodified",
4001: XMLWriter.NO_CONTENT);
4002: }
4003: generatedXML.writeElement(null, "resourcetype",
4004: XMLWriter.NO_CONTENT);
4005: generatedXML.writeElement(null, "source",
4006: XMLWriter.NO_CONTENT);
4007: generatedXML.writeElement(null, "lockdiscovery",
4008: XMLWriter.NO_CONTENT);
4009:
4010: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
4011: generatedXML
4012: .writeElement(null, "status", XMLWriter.OPENING);
4013: generatedXML.writeText(status);
4014: generatedXML
4015: .writeElement(null, "status", XMLWriter.CLOSING);
4016: generatedXML.writeElement(null, "propstat",
4017: XMLWriter.CLOSING);
4018:
4019: break;
4020:
4021: case FIND_BY_PROPERTY:
4022:
4023: Vector propertiesNotFound = new Vector();
4024:
4025: // Parse the list of properties
4026:
4027: generatedXML.writeElement(null, "propstat",
4028: XMLWriter.OPENING);
4029: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
4030:
4031: Enumeration properties = propertiesVector.elements();
4032:
4033: while (properties.hasMoreElements()) {
4034:
4035: String property = (String) properties.nextElement();
4036:
4037: if (property.equals("creationdate")) {
4038: generatedXML
4039: .writeProperty(
4040: null,
4041: "creationdate",
4042: getISOCreationDate(resourceInfo.creationDate));
4043: } else if (property.equals("displayname")) {
4044: generatedXML.writeElement(null, "displayname",
4045: XMLWriter.OPENING);
4046:
4047: generatedXML.writeData(resourceInfo.displayName);
4048: generatedXML.writeElement(null, "displayname",
4049: XMLWriter.CLOSING);
4050: } else if (property.equals("getcontentlanguage")) {
4051: if (resourceInfo.collection) {
4052: propertiesNotFound.addElement(property);
4053: } else {
4054: generatedXML.writeProperty(null,
4055: "getcontentlanguage", Locale
4056: .getDefault().toString());
4057: }
4058: } else if (property.equals("getcontentlength")) {
4059: if (resourceInfo.collection) {
4060: propertiesNotFound.addElement(property);
4061: } else {
4062: generatedXML.writeProperty(null,
4063: "getcontentlength", (String
4064: .valueOf(resourceInfo.length)));
4065: }
4066: } else if (property.equals("getcontenttype")) {
4067: if (resourceInfo.collection) {
4068: propertiesNotFound.addElement(property);
4069: } else {
4070: generatedXML
4071: .writeProperty(
4072: null,
4073: "getcontenttype",
4074: getServletContext()
4075: .getMimeType(
4076: resourceInfo.path));
4077: }
4078: } else if (property.equals("getetag")) {
4079: if (resourceInfo.collection) {
4080: propertiesNotFound.addElement(property);
4081: } else {
4082: generatedXML.writeProperty(null, "getetag",
4083: resourceInfo.eTag);
4084: // getETagValue(resourceInfo, true));
4085: }
4086: } else if (property.equals("getlastmodified")) {
4087: if (resourceInfo.collection) {
4088: propertiesNotFound.addElement(property);
4089: } else {
4090: generatedXML.writeProperty(null,
4091: "getlastmodified",
4092: resourceInfo.httpDate);
4093: }
4094: } else if (property.equals("resourcetype")) {
4095: if (resourceInfo.collection) {
4096: generatedXML.writeElement(null, "resourcetype",
4097: XMLWriter.OPENING);
4098: generatedXML.writeElement(null, "collection",
4099: XMLWriter.NO_CONTENT);
4100: generatedXML.writeElement(null, "resourcetype",
4101: XMLWriter.CLOSING);
4102: } else {
4103: generatedXML.writeElement(null, "resourcetype",
4104: XMLWriter.NO_CONTENT);
4105: }
4106: // iscollection is an MS property. Contribute uses it but seems to be
4107: // able to get along without it
4108: // } else if (property.equals("iscollection")) {
4109: // generatedXML.writeProperty
4110: // (null, "iscollection",
4111: // resourceInfo.collection ? "1" : "0");
4112: } else if (property.equals("source")) {
4113: generatedXML.writeProperty(null, "source", "");
4114: } else if (property.equals("supportedlock")) {
4115: supportedLocks = "<lockentry>"
4116: + "<lockscope><exclusive/></lockscope>"
4117: + "<locktype><write/></locktype>"
4118: + "</lockentry>" + "<lockentry>"
4119: + "<lockscope><shared/></lockscope>"
4120: + "<locktype><write/></locktype>"
4121: + "</lockentry>";
4122: generatedXML.writeElement(null, "supportedlock",
4123: XMLWriter.OPENING);
4124: generatedXML.writeText(supportedLocks);
4125: generatedXML.writeElement(null, "supportedlock",
4126: XMLWriter.CLOSING);
4127: } else if (property.equals("lockdiscovery")) {
4128: if (!generateLockDiscovery(path, generatedXML))
4129: propertiesNotFound.addElement(property);
4130: } else {
4131: propertiesNotFound.addElement(property);
4132: }
4133: }
4134:
4135: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
4136: generatedXML
4137: .writeElement(null, "status", XMLWriter.OPENING);
4138: generatedXML.writeText(status);
4139: generatedXML
4140: .writeElement(null, "status", XMLWriter.CLOSING);
4141: generatedXML.writeElement(null, "propstat",
4142: XMLWriter.CLOSING);
4143:
4144: Enumeration propertiesNotFoundList = propertiesNotFound
4145: .elements();
4146:
4147: if (propertiesNotFoundList.hasMoreElements()) {
4148: status = new String(
4149: "HTTP/1.1 "
4150: + SakaidavStatus.SC_NOT_FOUND
4151: + " "
4152: + SakaidavStatus
4153: .getStatusText(SakaidavStatus.SC_NOT_FOUND));
4154:
4155: generatedXML.writeElement(null, "propstat",
4156: XMLWriter.OPENING);
4157: generatedXML.writeElement(null, "prop",
4158: XMLWriter.OPENING);
4159:
4160: while (propertiesNotFoundList.hasMoreElements()) {
4161: generatedXML.writeElement(null,
4162: (String) propertiesNotFoundList
4163: .nextElement(),
4164: XMLWriter.NO_CONTENT);
4165: }
4166:
4167: generatedXML.writeElement(null, "prop",
4168: XMLWriter.CLOSING);
4169: generatedXML.writeElement(null, "status",
4170: XMLWriter.OPENING);
4171: generatedXML.writeText(status);
4172: generatedXML.writeElement(null, "status",
4173: XMLWriter.CLOSING);
4174: generatedXML.writeElement(null, "propstat",
4175: XMLWriter.CLOSING);
4176:
4177: }
4178: break;
4179:
4180: }
4181:
4182: generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
4183:
4184: }
4185:
4186: /**
4187: * Propfind helper method. Dispays the properties of a lock-null resource.
4188: *
4189: * @param resources
4190: * Resources object associated with this context
4191: * @param generatedXML
4192: * XML response to the Propfind request
4193: * @param path
4194: * Path of the current resource
4195: * @param type
4196: * Propfind type
4197: * @param propertiesVector
4198: * If the propfind type is find properties by name, then this Vector contains those properties
4199: */
4200: private void parseLockNullProperties(HttpServletRequest req,
4201: XMLWriter generatedXML, String path, int type,
4202: Vector propertiesVector) {
4203:
4204: // Exclude any resource in the /WEB-INF and /META-INF subdirectories
4205: // (the "toUpperCase()" avoids problems on Windows systems)
4206: if (path.toUpperCase().startsWith("/WEB-INF")
4207: || path.toUpperCase().startsWith("/META-INF"))
4208: return;
4209:
4210: // Retrieving the lock associated with the lock-null resource
4211: LockInfo lock = (LockInfo) resourceLocks.get(path);
4212:
4213: if (lock == null)
4214: return;
4215:
4216: generatedXML.writeElement(null, "response", XMLWriter.OPENING);
4217: String status = new String("HTTP/1.1 " + SakaidavStatus.SC_OK
4218: + " "
4219: + SakaidavStatus.getStatusText(SakaidavStatus.SC_OK));
4220:
4221: // Generating href element
4222: generatedXML.writeElement(null, "href", XMLWriter.OPENING);
4223:
4224: String absoluteUri = req.getRequestURI();
4225: String relativePath = getRelativePath(req);
4226: String toAppend = path.substring(relativePath.length());
4227: if (!toAppend.startsWith("/"))
4228: toAppend = "/" + toAppend;
4229:
4230: generatedXML.writeText(rewriteUrl(normalize(absoluteUri
4231: + toAppend)));
4232:
4233: generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
4234:
4235: String resourceName = justName(path);
4236:
4237: switch (type) {
4238:
4239: case FIND_ALL_PROP:
4240:
4241: generatedXML.writeElement(null, "propstat",
4242: XMLWriter.OPENING);
4243: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
4244:
4245: generatedXML.writeProperty(null, "creationdate",
4246: getISOCreationDate(lock.creationDate.getTime()));
4247: generatedXML.writeElement(null, "displayname",
4248: XMLWriter.OPENING);
4249: generatedXML.writeData(resourceName);
4250: generatedXML.writeElement(null, "displayname",
4251: XMLWriter.CLOSING);
4252: generatedXML.writeProperty(null, "getcontentlanguage",
4253: Locale.getDefault().toString());
4254: generatedXML.writeProperty(null, "getlastmodified",
4255: formats[0].format(lock.creationDate));
4256: generatedXML.writeProperty(null, "getcontentlength", String
4257: .valueOf(0));
4258: generatedXML.writeProperty(null, "getcontenttype", "");
4259: generatedXML.writeProperty(null, "getetag", "");
4260: generatedXML.writeElement(null, "resourcetype",
4261: XMLWriter.OPENING);
4262: generatedXML.writeElement(null, "lock-null",
4263: XMLWriter.NO_CONTENT);
4264: generatedXML.writeElement(null, "resourcetype",
4265: XMLWriter.CLOSING);
4266:
4267: generatedXML.writeProperty(null, "source", "");
4268:
4269: String supportedLocks = "<lockentry>"
4270: + "<lockscope><exclusive/></lockscope>"
4271: + "<locktype><write/></locktype>" + "</lockentry>"
4272: + "<lockentry>"
4273: + "<lockscope><shared/></lockscope>"
4274: + "<locktype><write/></locktype>" + "</lockentry>";
4275: generatedXML.writeElement(null, "supportedlock",
4276: XMLWriter.OPENING);
4277: generatedXML.writeText(supportedLocks);
4278: generatedXML.writeElement(null, "supportedlock",
4279: XMLWriter.CLOSING);
4280:
4281: generateLockDiscovery(path, generatedXML);
4282:
4283: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
4284: generatedXML
4285: .writeElement(null, "status", XMLWriter.OPENING);
4286: generatedXML.writeText(status);
4287: generatedXML
4288: .writeElement(null, "status", XMLWriter.CLOSING);
4289: generatedXML.writeElement(null, "propstat",
4290: XMLWriter.CLOSING);
4291:
4292: break;
4293:
4294: case FIND_PROPERTY_NAMES:
4295:
4296: generatedXML.writeElement(null, "propstat",
4297: XMLWriter.OPENING);
4298: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
4299:
4300: generatedXML.writeElement(null, "creationdate",
4301: XMLWriter.NO_CONTENT);
4302: generatedXML.writeElement(null, "displayname",
4303: XMLWriter.NO_CONTENT);
4304: generatedXML.writeElement(null, "getcontentlanguage",
4305: XMLWriter.NO_CONTENT);
4306: generatedXML.writeElement(null, "getcontentlength",
4307: XMLWriter.NO_CONTENT);
4308: generatedXML.writeElement(null, "getcontenttype",
4309: XMLWriter.NO_CONTENT);
4310: generatedXML.writeElement(null, "getetag",
4311: XMLWriter.NO_CONTENT);
4312: generatedXML.writeElement(null, "getlastmodified",
4313: XMLWriter.NO_CONTENT);
4314: generatedXML.writeElement(null, "resourcetype",
4315: XMLWriter.NO_CONTENT);
4316: generatedXML.writeElement(null, "source",
4317: XMLWriter.NO_CONTENT);
4318: generatedXML.writeElement(null, "lockdiscovery",
4319: XMLWriter.NO_CONTENT);
4320:
4321: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
4322: generatedXML
4323: .writeElement(null, "status", XMLWriter.OPENING);
4324: generatedXML.writeText(status);
4325: generatedXML
4326: .writeElement(null, "status", XMLWriter.CLOSING);
4327: generatedXML.writeElement(null, "propstat",
4328: XMLWriter.CLOSING);
4329:
4330: break;
4331:
4332: case FIND_BY_PROPERTY:
4333:
4334: Vector propertiesNotFound = new Vector();
4335:
4336: // Parse the list of properties
4337:
4338: generatedXML.writeElement(null, "propstat",
4339: XMLWriter.OPENING);
4340: generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
4341:
4342: Enumeration properties = propertiesVector.elements();
4343:
4344: while (properties.hasMoreElements()) {
4345:
4346: String property = (String) properties.nextElement();
4347:
4348: if (property.equals("creationdate")) {
4349: generatedXML.writeProperty(null, "creationdate",
4350: getISOCreationDate(lock.creationDate
4351: .getTime()));
4352: } else if (property.equals("displayname")) {
4353: generatedXML.writeElement(null, "displayname",
4354: XMLWriter.OPENING);
4355: generatedXML.writeData(resourceName);
4356: generatedXML.writeElement(null, "displayname",
4357: XMLWriter.CLOSING);
4358: } else if (property.equals("getcontentlanguage")) {
4359: generatedXML.writeProperty(null,
4360: "getcontentlanguage", Locale.getDefault()
4361: .toString());
4362: } else if (property.equals("getcontentlength")) {
4363: generatedXML.writeProperty(null,
4364: "getcontentlength", (String.valueOf(0)));
4365: } else if (property.equals("getcontenttype")) {
4366: generatedXML.writeProperty(null, "getcontenttype",
4367: "");
4368: } else if (property.equals("getetag")) {
4369: generatedXML.writeProperty(null, "getetag", "");
4370: } else if (property.equals("getlastmodified")) {
4371: generatedXML.writeProperty(null, "getlastmodified",
4372: formats[0].format(lock.creationDate));
4373: } else if (property.equals("resourcetype")) {
4374: generatedXML.writeElement(null, "resourcetype",
4375: XMLWriter.OPENING);
4376: generatedXML.writeElement(null, "lock-null",
4377: XMLWriter.NO_CONTENT);
4378: generatedXML.writeElement(null, "resourcetype",
4379: XMLWriter.CLOSING);
4380: } else if (property.equals("source")) {
4381: generatedXML.writeProperty(null, "source", "");
4382: } else if (property.equals("supportedlock")) {
4383: supportedLocks = "<lockentry>"
4384: + "<lockscope><exclusive/></lockscope>"
4385: + "<locktype><write/></locktype>"
4386: + "</lockentry>" + "<lockentry>"
4387: + "<lockscope><shared/></lockscope>"
4388: + "<locktype><write/></locktype>"
4389: + "</lockentry>";
4390: generatedXML.writeElement(null, "supportedlock",
4391: XMLWriter.OPENING);
4392: generatedXML.writeText(supportedLocks);
4393: generatedXML.writeElement(null, "supportedlock",
4394: XMLWriter.CLOSING);
4395: } else if (property.equals("lockdiscovery")) {
4396: if (!generateLockDiscovery(path, generatedXML))
4397: propertiesNotFound.addElement(property);
4398: } else {
4399: propertiesNotFound.addElement(property);
4400: }
4401:
4402: }
4403:
4404: generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
4405: generatedXML
4406: .writeElement(null, "status", XMLWriter.OPENING);
4407: generatedXML.writeText(status);
4408: generatedXML
4409: .writeElement(null, "status", XMLWriter.CLOSING);
4410: generatedXML.writeElement(null, "propstat",
4411: XMLWriter.CLOSING);
4412:
4413: Enumeration propertiesNotFoundList = propertiesNotFound
4414: .elements();
4415:
4416: if (propertiesNotFoundList.hasMoreElements()) {
4417:
4418: status = new String(
4419: "HTTP/1.1 "
4420: + SakaidavStatus.SC_NOT_FOUND
4421: + " "
4422: + SakaidavStatus
4423: .getStatusText(SakaidavStatus.SC_NOT_FOUND));
4424:
4425: generatedXML.writeElement(null, "propstat",
4426: XMLWriter.OPENING);
4427: generatedXML.writeElement(null, "prop",
4428: XMLWriter.OPENING);
4429:
4430: while (propertiesNotFoundList.hasMoreElements()) {
4431: generatedXML.writeElement(null,
4432: (String) propertiesNotFoundList
4433: .nextElement(),
4434: XMLWriter.NO_CONTENT);
4435: }
4436:
4437: generatedXML.writeElement(null, "prop",
4438: XMLWriter.CLOSING);
4439: generatedXML.writeElement(null, "status",
4440: XMLWriter.OPENING);
4441: generatedXML.writeText(status);
4442: generatedXML.writeElement(null, "status",
4443: XMLWriter.CLOSING);
4444: generatedXML.writeElement(null, "propstat",
4445: XMLWriter.CLOSING);
4446:
4447: }
4448:
4449: break;
4450:
4451: }
4452:
4453: generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
4454:
4455: }
4456:
4457: /**
4458: * Print the lock discovery information associated with a path.
4459: *
4460: * @param path
4461: * Path
4462: * @param generatedXML
4463: * XML data to which the locks info will be appended
4464: * @return true if at least one lock was displayed
4465: */
4466: private boolean generateLockDiscovery(String path,
4467: XMLWriter generatedXML) {
4468:
4469: LockInfo resourceLock = (LockInfo) resourceLocks.get(path);
4470: Enumeration collectionLocksList = collectionLocks.elements();
4471:
4472: boolean wroteStart = false;
4473:
4474: if (resourceLock != null) {
4475: wroteStart = true;
4476: generatedXML.writeElement(null, "lockdiscovery",
4477: XMLWriter.OPENING);
4478: resourceLock.toXML(generatedXML);
4479: }
4480:
4481: while (collectionLocksList.hasMoreElements()) {
4482: LockInfo currentLock = (LockInfo) collectionLocksList
4483: .nextElement();
4484: if (path.startsWith(currentLock.path)) {
4485: if (!wroteStart) {
4486: wroteStart = true;
4487: generatedXML.writeElement(null, "lockdiscovery",
4488: XMLWriter.OPENING);
4489: }
4490: currentLock.toXML(generatedXML);
4491: }
4492: }
4493:
4494: if (wroteStart) {
4495: generatedXML.writeElement(null, "lockdiscovery",
4496: XMLWriter.CLOSING);
4497: } else {
4498: return false;
4499: }
4500:
4501: return true;
4502:
4503: }
4504:
4505: /**
4506: * Get creation date in ISO format.
4507: */
4508: private String getISOCreationDate(long creationDate) {
4509: StringBuffer creationDateValue = new StringBuffer(
4510: creationDateFormat.format(new Date(creationDate)));
4511: /*
4512: * int offset = Calendar.getInstance().getTimeZone().getRawOffset() / 3600000; // FIXME ? if (offset < 0) { creationDateValue.append("-"); offset = -offset; } else if (offset > 0) { creationDateValue.append("+"); } if (offset != 0) { if (offset <
4513: * 10) creationDateValue.append("0"); creationDateValue.append(offset + ":00"); } else { creationDateValue.append("Z"); }
4514: */
4515: return creationDateValue.toString();
4516: }
4517:
4518: /**
4519: * Get a date in HTTP format.
4520: */
4521: private String getHttpDate(long dateMS) {
4522: return HttpDateFormat.format(new Date(dateMS));
4523: }
4524:
4525: // -------------------------------------------------- LockInfo Inner Class
4526:
4527: /**
4528: * Holds a lock information.
4529: */
4530: private class LockInfo {
4531:
4532: // -------------------------------------------------------- Constructor
4533:
4534: /**
4535: * Constructor.
4536: *
4537: * @param pathname
4538: * Path name of the file
4539: */
4540: public LockInfo() {
4541:
4542: }
4543:
4544: // ------------------------------------------------- Instance Variables
4545:
4546: String path = "/";
4547:
4548: String type = "write";
4549:
4550: String scope = "exclusive";
4551:
4552: int depth = 0;
4553:
4554: String owner = "";
4555:
4556: Vector tokens = new Vector();
4557:
4558: long expiresAt = 0;
4559:
4560: Date creationDate = new Date();
4561:
4562: // ----------------------------------------------------- Public Methods
4563:
4564: /**
4565: * Get a String representation of this lock token.
4566: */
4567: public String toString() {
4568:
4569: String result = "Type:" + type + "\n";
4570: result += "Scope:" + scope + "\n";
4571: result += "Depth:" + depth + "\n";
4572: result += "Owner:" + owner + "\n";
4573: result += "Expiration:"
4574: + formats[0].format(new Date(expiresAt)) + "\n";
4575: Enumeration tokensList = tokens.elements();
4576: while (tokensList.hasMoreElements()) {
4577: result += "Token:" + tokensList.nextElement() + "\n";
4578: }
4579: return result;
4580:
4581: }
4582:
4583: /**
4584: * Return true if the lock has expired.
4585: */
4586: public boolean hasExpired() {
4587: return (System.currentTimeMillis() > expiresAt);
4588: }
4589:
4590: /**
4591: * Return true if the lock is exclusive.
4592: */
4593: public boolean isExclusive() {
4594:
4595: return (scope.equals("exclusive"));
4596:
4597: }
4598:
4599: /**
4600: * Get an XML representation of this lock token. This method will append an XML fragment to the given XML writer.
4601: */
4602: // originally this called toXML( ... false). That causes
4603: // the system to show a dummy lock name. That breaks
4604: // Contribute. It also violates the RFC. Contribute uses
4605: // PROPFIND to find the lock name. Furthermore, the RFC
4606: // specifically prohibits hiding the lock name this way.
4607: // Rather than treating the lock name as secret, it's
4608: // better to check permissions, as I now do.
4609: public void toXML(XMLWriter generatedXML) {
4610: toXML(generatedXML, true);
4611: }
4612:
4613: /**
4614: * Get an XML representation of this lock token. This method will append an XML fragment to the given XML writer.
4615: */
4616: public void toXML(XMLWriter generatedXML, boolean showToken) {
4617:
4618: generatedXML.writeElement(null, "activelock",
4619: XMLWriter.OPENING);
4620:
4621: generatedXML.writeElement(null, "locktype",
4622: XMLWriter.OPENING);
4623: generatedXML.writeElement(null, type, XMLWriter.NO_CONTENT);
4624: generatedXML.writeElement(null, "locktype",
4625: XMLWriter.CLOSING);
4626:
4627: generatedXML.writeElement(null, "lockscope",
4628: XMLWriter.OPENING);
4629: generatedXML
4630: .writeElement(null, scope, XMLWriter.NO_CONTENT);
4631: generatedXML.writeElement(null, "lockscope",
4632: XMLWriter.CLOSING);
4633:
4634: generatedXML.writeElement(null, "depth", XMLWriter.OPENING);
4635: if (depth == INFINITY) {
4636: generatedXML.writeText("Infinity");
4637: } else {
4638: generatedXML.writeText("0");
4639: }
4640: generatedXML.writeElement(null, "depth", XMLWriter.CLOSING);
4641:
4642: generatedXML.writeElement(null, "owner", XMLWriter.OPENING);
4643: generatedXML.writeText(owner);
4644: generatedXML.writeElement(null, "owner", XMLWriter.CLOSING);
4645:
4646: generatedXML.writeElement(null, "timeout",
4647: XMLWriter.OPENING);
4648: long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
4649: generatedXML.writeText("Second-" + timeout);
4650: generatedXML.writeElement(null, "timeout",
4651: XMLWriter.CLOSING);
4652:
4653: generatedXML.writeElement(null, "locktoken",
4654: XMLWriter.OPENING);
4655: if (showToken) {
4656: Enumeration tokensList = tokens.elements();
4657: while (tokensList.hasMoreElements()) {
4658: generatedXML.writeElement(null, "href",
4659: XMLWriter.OPENING);
4660: generatedXML.writeText("opaquelocktoken:"
4661: + tokensList.nextElement());
4662: generatedXML.writeElement(null, "href",
4663: XMLWriter.CLOSING);
4664: }
4665: } else {
4666: generatedXML.writeElement(null, "href",
4667: XMLWriter.OPENING);
4668: generatedXML.writeText("opaquelocktoken:dummytoken");
4669: generatedXML.writeElement(null, "href",
4670: XMLWriter.CLOSING);
4671: }
4672: generatedXML.writeElement(null, "locktoken",
4673: XMLWriter.CLOSING);
4674:
4675: generatedXML.writeElement(null, "activelock",
4676: XMLWriter.CLOSING);
4677:
4678: }
4679:
4680: }
4681:
4682: // --------------------------------------------------- Property Inner Class
4683:
4684: private class Property {
4685:
4686: public String name;
4687:
4688: public String value;
4689:
4690: public String namespace;
4691:
4692: public String namespaceAbbrev;
4693:
4694: public int status = SakaidavStatus.SC_OK;
4695:
4696: }
4697:
4698: };
4699:
4700: // -------------------------------------------------------- SakaidavStatus Class
4701:
4702: /**
4703: * Wraps the HttpServletResponse class to abstract the specific protocol used. To support other protocols we would only need to modify this class and the SakaidavRetCode classes.
4704: *
4705: * @author Marc Eaddy
4706: * @version 1.0, 16 Nov 1997
4707: */
4708: class SakaidavStatus {
4709:
4710: // ----------------------------------------------------- Instance Variables
4711:
4712: /**
4713: * This Hashtable contains the mapping of HTTP and Sakaidav status codes to descriptive text. This is a static variable.
4714: */
4715: private static Hashtable mapStatusCodes = new Hashtable();
4716:
4717: // ------------------------------------------------------ HTTP Status Codes
4718:
4719: /**
4720: * Status code (200) indicating the request succeeded normally.
4721: */
4722: public static final int SC_OK = HttpServletResponse.SC_OK;
4723:
4724: /**
4725: * Status code (201) indicating the request succeeded and created a new resource on the server.
4726: */
4727: public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
4728:
4729: /**
4730: * Status code (202) indicating that a request was accepted for processing, but was not completed.
4731: */
4732: public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
4733:
4734: /**
4735: * Status code (204) indicating that the request succeeded but that there was no new information to return.
4736: */
4737: public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
4738:
4739: /**
4740: * Status code (301) indicating that the resource has permanently moved to a new location, and that future references should use a new URI with their requests.
4741: */
4742: public static final int SC_MOVED_PERMANENTLY = HttpServletResponse.SC_MOVED_PERMANENTLY;
4743:
4744: /**
4745: * Status code (302) indicating that the resource has temporarily moved to another location, but that future references should still use the original URI to access the resource.
4746: */
4747: public static final int SC_MOVED_TEMPORARILY = HttpServletResponse.SC_MOVED_TEMPORARILY;
4748:
4749: /**
4750: * Status code (304) indicating that a conditional GET operation found that the resource was available and not modified.
4751: */
4752: public static final int SC_NOT_MODIFIED = HttpServletResponse.SC_NOT_MODIFIED;
4753:
4754: /**
4755: * Status code (400) indicating the request sent by the client was syntactically incorrect.
4756: */
4757: public static final int SC_BAD_REQUEST = HttpServletResponse.SC_BAD_REQUEST;
4758:
4759: /**
4760: * Status code (401) indicating that the request requires HTTP authentication.
4761: */
4762: public static final int SC_UNAUTHORIZED = HttpServletResponse.SC_UNAUTHORIZED;
4763:
4764: /**
4765: * Status code (403) indicating the server understood the request but refused to fulfill it.
4766: */
4767: public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
4768:
4769: /**
4770: * Status code (404) indicating that the requested resource is not available.
4771: */
4772: public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
4773:
4774: /**
4775: * Status code (500) indicating an error inside the HTTP service which prevented it from fulfilling the request.
4776: */
4777: public static final int SC_INTERNAL_SERVER_ERROR = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
4778:
4779: /**
4780: * Status code (501) indicating the HTTP service does not support the functionality needed to fulfill the request.
4781: */
4782: public static final int SC_NOT_IMPLEMENTED = HttpServletResponse.SC_NOT_IMPLEMENTED;
4783:
4784: /**
4785: * Status code (502) indicating that the HTTP server received an invalid response from a server it consulted when acting as a proxy or gateway.
4786: */
4787: public static final int SC_BAD_GATEWAY = HttpServletResponse.SC_BAD_GATEWAY;
4788:
4789: /**
4790: * Status code (503) indicating that the HTTP service is temporarily overloaded, and unable to handle the request.
4791: */
4792: public static final int SC_SERVICE_UNAVAILABLE = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
4793:
4794: /**
4795: * Status code (100) indicating the client may continue with its request. This interim response is used to inform the client that the initial part of the request has been received and has not yet been rejected by the server.
4796: */
4797: public static final int SC_CONTINUE = 100;
4798:
4799: /**
4800: * Status code (405) indicating the method specified is not allowed for the resource.
4801: */
4802: public static final int SC_METHOD_NOT_ALLOWED = 405;
4803:
4804: /**
4805: * Status code (409) indicating that the request could not be completed due to a conflict with the current state of the resource.
4806: */
4807: public static final int SC_CONFLICT = 409;
4808:
4809: /**
4810: * Status code (412) indicating the precondition given in one or more of the request-header fields evaluated to false when it was tested on the server.
4811: */
4812: public static final int SC_PRECONDITION_FAILED = 412;
4813:
4814: /**
4815: * Status code (413) indicating the server is refusing to process a request because the request entity is larger than the server is willing or able to process.
4816: */
4817: public static final int SC_REQUEST_TOO_LONG = 413;
4818:
4819: /**
4820: * Status code (415) indicating the server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method.
4821: */
4822: public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
4823:
4824: // -------------------------------------------- Extended Sakaidav status code
4825:
4826: /**
4827: * Status code (207) indicating that the response requires providing status for multiple independent operations.
4828: */
4829: public static final int SC_MULTI_STATUS = 207;
4830:
4831: // This one colides with HTTP 1.1
4832: // "207 Parital Update OK"
4833:
4834: /**
4835: * Status code (418) indicating the entity body submitted with the PATCH method was not understood by the resource.
4836: */
4837: public static final int SC_UNPROCESSABLE_ENTITY = 418;
4838:
4839: // This one colides with HTTP 1.1
4840: // "418 Reauthentication Required"
4841:
4842: /**
4843: * Status code (419) indicating that the resource does not have sufficient space to record the state of the resource after the execution of this method.
4844: */
4845: public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
4846:
4847: // This one colides with HTTP 1.1
4848: // "419 Proxy Reauthentication Required"
4849:
4850: /**
4851: * Status code (420) indicating the method was not executed on a particular resource within its scope because some part of the method's execution failed causing the entire method to be aborted.
4852: */
4853: public static final int SC_METHOD_FAILURE = 420;
4854:
4855: /**
4856: * Status code (423) indicating the destination resource of a method is locked, and either the request did not contain a valid Lock-Info header, or the Lock-Info header identifies a lock held by another principal.
4857: */
4858: public static final int SC_LOCKED = 423;
4859:
4860: // ------------------------------------------------------------ Initializer
4861:
4862: static {
4863: // HTTP 1.0 tatus Code
4864: addStatusCodeMap(SC_OK, "OK");
4865: addStatusCodeMap(SC_CREATED, "Created");
4866: addStatusCodeMap(SC_ACCEPTED, "Accepted");
4867: addStatusCodeMap(SC_NO_CONTENT, "No Content");
4868: addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
4869: addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
4870: addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
4871: addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
4872: addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
4873: addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
4874: addStatusCodeMap(SC_NOT_FOUND, "Not Found");
4875: addStatusCodeMap(SC_INTERNAL_SERVER_ERROR,
4876: "Internal Server Error");
4877: addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
4878: addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
4879: addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
4880: addStatusCodeMap(SC_CONTINUE, "Continue");
4881: addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
4882: addStatusCodeMap(SC_CONFLICT, "Conflict");
4883: addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
4884: addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
4885: addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE,
4886: "Unsupported Media Type");
4887: // dav Status Codes
4888: addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
4889: addStatusCodeMap(SC_UNPROCESSABLE_ENTITY,
4890: "Unprocessable Entity");
4891: addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE,
4892: "Insufficient Space On Resource");
4893: addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
4894: addStatusCodeMap(SC_LOCKED, "Locked");
4895: }
4896:
4897: // --------------------------------------------------------- Public Methods
4898:
4899: /**
4900: * Returns the HTTP status text for the HTTP or WebDav status code specified by looking it up in the static mapping. This is a static function.
4901: *
4902: * @param nHttpStatusCode
4903: * [IN] HTTP or WebDAV status code
4904: * @return A string with a short descriptive phrase for the HTTP status code (e.g., "OK").
4905: */
4906: public static String getStatusText(int nHttpStatusCode) {
4907: Integer intKey = new Integer(nHttpStatusCode);
4908:
4909: if (!mapStatusCodes.containsKey(intKey)) {
4910: return "";
4911: } else {
4912: return (String) mapStatusCodes.get(intKey);
4913: }
4914: }
4915:
4916: // -------------------------------------------------------- Private Methods
4917:
4918: /**
4919: * Adds a new status code -> status text mapping. This is a static method because the mapping is a static variable.
4920: *
4921: * @param nKey
4922: * [IN] HTTP or WebDAV status code
4923: * @param strVal
4924: * [IN] HTTP status text
4925: */
4926: private static void addStatusCodeMap(int nKey, String strVal) {
4927: mapStatusCodes.put(new Integer(nKey), strVal);
4928: }
4929: };
|