/*
* ConcourseConnect
* Copyright 2009 Concursive Corporation
* http://www.concursive.com
*
* This file is part of ConcourseConnect, an open source social business
* software and community platform.
*
* Concursive ConcourseConnect is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, version 3 of the License.
*
* Under the terms of the GNU Affero General Public License you must release the
* complete source code for any application that uses any part of ConcourseConnect
* (system header files and libraries used by the operating system are excluded).
* These terms must be included in any work that has ConcourseConnect components.
* If you are developing and distributing open source applications under the
* GNU Affero General Public License, then you are free to use ConcourseConnect
* under the GNU Affero General Public License.
*
* If you are deploying a web site in which users interact with any portion of
* ConcourseConnect over a network, the complete source code changes must be made
* available. For example, include a link to the source archive directly from
* your web site.
*
* For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their
* products, and do not license and distribute their source code under the GNU
* Affero General Public License, Concursive provides a flexible commercial
* license.
*
* To anyone in doubt, we recommend the commercial license. Our commercial license
* is competitively priced and will eliminate any confusion about how
* ConcourseConnect can be used and distributed.
*
* ConcourseConnect is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with ConcourseConnect. If not, see <http://www.gnu.org/licenses/>.
*
* Attribution Notice: ConcourseConnect is an Original Work of software created
* by Concursive Corporation
*/
package com.concursive.connect.web.webdav.servlets;
import com.concursive.commons.db.ConnectionElement;
import com.concursive.commons.web.mvc.actions.ActionContext;
import com.concursive.connect.config.ApplicationPrefs;
import com.concursive.connect.web.webdav.WebdavManager;
import com.concursive.connect.web.webdav.context.ModuleContext;
import org.apache.catalina.servlets.DefaultServlet;
import org.apache.catalina.util.DOMWriter;
import org.apache.catalina.util.MD5Encoder;
import org.apache.catalina.util.RequestUtil;
import org.apache.catalina.util.XMLWriter;
import org.apache.commons.codec.binary.Base64;
import org.apache.naming.resources.Resource;
import org.apache.tomcat.util.http.FastHttpDateFormat;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.sql.Connection;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Servlet which adds support for WebDAV level 2. All the basic HTTP requests
* are handled by the DefaultServlet.
*
* @author Remy Maucherat
* @version $Revision$ $Date$
* @created July 12, 2004
*/
public class WebdavServlet
extends DefaultServlet {
// -------------------------------------------------------------- Constants
private final static String METHOD_HEAD = "HEAD";
private final static String METHOD_OPTIONS = "OPTIONS";
private final static String METHOD_PROPFIND = "PROPFIND";
private final static String METHOD_PROPPATCH = "PROPPATCH";
private final static String METHOD_MKCOL = "MKCOL";
private final static String METHOD_COPY = "COPY";
private final static String METHOD_MOVE = "MOVE";
private final static String METHOD_LOCK = "LOCK";
private final static String METHOD_UNLOCK = "UNLOCK";
/**
* USER_REALM should never be changed since when a user is added to the
* system this value is used in calculating the user's webdav password.
* changing the realm will invalidate all the users accessing webdav
*/
public final static String USER_REALM = "ConcourseConnect";
/**
* List of systems initialized if system is in asp mode
*/
private HashMap systemsInitialized = new HashMap();
/**
* Default depth is infite.
*/
private final static int INFINITY = 3;
// To limit tree browsing a bit
/**
* PROPFIND - Specify a property mask.
*/
private final static int FIND_BY_PROPERTY = 0;
/**
* PROPFIND - Display all properties.
*/
private final static int FIND_ALL_PROP = 1;
/**
* PROPFIND - Return property names.
*/
private final static int FIND_PROPERTY_NAMES = 2;
/**
* Create a new lock.
*/
private final static int LOCK_CREATION = 0;
/**
* Refresh lock.
*/
private final static int LOCK_REFRESH = 1;
/**
* Default lock timeout value.
*/
private final static int DEFAULT_TIMEOUT = 3600;
/**
* Maximum lock timeout.
*/
private final static int MAX_TIMEOUT = 604800;
/**
* Default namespace.
*/
protected final static String DEFAULT_NAMESPACE = "DAV:";
/**
* Simple date format for the creation date ISO representation (partial).
*/
protected final static SimpleDateFormat creationDateFormat =
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
static {
creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
}
// ----------------------------------------------------- Instance Variables
/**
* Repository of the locks put on single resources. <p>
* <p/>
* Key : path <br>
* Value : LockInfo
*/
private Hashtable resourceLocks = new Hashtable();
/**
* Repository of the lock-null resources. <p>
* <p/>
* Key : path of the collection containing the lock-null resource<br>
* 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.
*/
private Hashtable lockNullResources = new Hashtable();
/**
* Vector of the heritable locks. <p>
* <p/>
* Key : path <br>
* Value : LockInfo
*/
private Vector collectionLocks = new Vector();
/**
* Secret information used to generate reasonably secure lock ids.
*/
private String secret = "catalina";
// --------------------------------------------------------- Public Methods
/**
* Initialize this servlet.
*
* @throws javax.servlet.ServletException Description of the Exception
*/
public void init()
throws ServletException {
super.init();
ServletConfig config = getServletConfig();
String value = null;
try {
value = config.getInitParameter("secret");
if (value != null) {
secret = value;
}
} catch (Throwable t) {
;
}
}
// ------------------------------------------------------ Protected Methods
/**
* Return JAXP document builder instance.
*
* @return The documentBuilder value
* @throws javax.servlet.ServletException Description of the Exception
*/
protected DocumentBuilder getDocumentBuilder()
throws ServletException {
DocumentBuilder documentBuilder = null;
DocumentBuilderFactory documentBuilderFactory = null;
try {
documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setExpandEntityReferences(false);
documentBuilder = documentBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new ServletException
(sm.getString("webdavservlet.jaxpfailed"));
}
return documentBuilder;
}
/**
* Description of the Method
*
* @param context Description of the Parameter
* @return Description of the Return Value
*/
protected synchronized boolean systemInitialized(ActionContext context) {
String serverName = context.getRequest().getServerName();
return (systemsInitialized.containsKey(serverName));
}
/**
* Description of the Method
*
* @param context Description of the Parameter
* @throws java.io.IOException Description of the Exception
*/
protected synchronized void initializeSystem(ActionContext context) throws IOException {
Connection db = null;
String serverName = context.getRequest().getServerName();
try {
if (System.getProperty("DEBUG") != null) {
System.out.println("WebdavServlet-> go to retrieveConnectionElement");
}
ConnectionElement ce = this.getConnectionElement(context);
if (System.getProperty("DEBUG") != null) {
System.out.println("WebdavServlet-> get connection");
}
db = this.getConnection(context);
if (System.getProperty("DEBUG") != null) {
System.out.println("WebdavServlet-> Check system status");
}
systemsInitialized.put(serverName, new Boolean(true));
// Load the modules that are available
WebdavManager webdavManager = (WebdavManager) context.getServletContext().getAttribute("WebdavManager");
if (webdavManager != null) {
ApplicationPrefs prefs = (ApplicationPrefs) context.getServletContext().getAttribute("applicationPrefs");
webdavManager.buildModules(db, prefs.get("FILELIBRARY"));
}
if (System.getProperty("DEBUG") != null) {
System.out.println("WebdavServlet-> system initialized.");
}
} catch (SQLException e) {
e.printStackTrace(System.out);
context.getResponse().sendError(SQLERROR, e.getMessage());
} finally {
this.freeConnection(db, context);
}
}
/**
* A2 is one of the components used in re-calculating the request digest at
* the server end
*
* @param context Description of the Parameter
* @param params Description of the Parameter
* @return The a2 value
*/
protected String getA2(ActionContext context, HashMap params) {
String method = context.getRequest().getMethod();
String uri = (String) params.get("uri");
return md5Encoder.encode(md5Helper.digest((method + ":" + uri).getBytes()));
}
/**
* Description of the Method
*
* @param argHeader Description of the Parameter
* @param context Description of the Parameter
* @return Description of the Return Value
* @throws java.io.IOException Description of the Exception
*/
protected boolean allowUser(ActionContext context, String argHeader) throws IOException {
if (argHeader == null) {
return false;
}
boolean status = false;
//System.out.println("WebdavServlet-> USERAGENT: " + context.getRequest().getHeader("user-agent"));
if (context.getRequest().getHeader("user-agent").toLowerCase().startsWith("microsoft")
|| context.getRequest().getHeader("user-agent").toLowerCase().indexOf("windows") > -1) {
//BASIC AUTHENTICATION
if (!argHeader.toUpperCase().startsWith("BASIC")) {
return false; // we only do BASIC
}
// Get encoded user and password, comes after "BASIC "
String userpassEncoded = argHeader.substring(6);
// Decode it, using any base 64 decoder
String userpassDecoded = new String(Base64.decodeBase64(userpassEncoded.getBytes("UTF8")));
// Check our user list to see if that user and password are "allowed"
String username = userpassDecoded.substring(0, userpassDecoded.indexOf(":"));
String password = userpassDecoded.substring(username.length() + 1);
Connection db = null;
try {
db = this.getConnection(context);
WebdavManager manager = this.getWebdavManager(context);
//System.out.println("username: " + username);
if (manager.hasUser(username)) {
status = true;
} else if (manager.allowUser(db, username, password)) {
String nonce = "";
status = manager.addUser(db, username, nonce);
}
} catch (SQLException e) {
e.printStackTrace(System.out);
//TODO: set error message in the response
} finally {
this.freeConnection(db, context);
}
} else {
//DIGEST AUTHENTICATION
if (!argHeader.startsWith("Digest")) {
return false;
}
HashMap params = getAuthenticationParams(context, argHeader);
//System.out.println("client digest : " + (String) params.get("response"));
Connection db = null;
String username = (String) params.get("username");
//System.out.println("username: " + username);
try {
WebdavManager manager = this.getWebdavManager(context);
if (manager.hasUser(username)) {
// user already logged in. calculate the digest value and validate the user
String digest =
MD5Encoder.encode(
md5Helper.digest(
(manager.getUser(username).getDigest() + ":" +
(String) params.get("nonce") + ":" + getA2(context, params)).getBytes()));
if (digest.equals((String) params.get("response"))) {
// user has been authenticated earlier and is still valid
status = true;
} else {
// user is no longer valid.
manager.removeUser(username);
status = false;
}
//System.out.println("server digest : " + digest);
} else {
// Try to authenticate the user
db = this.getConnection(context);
String digest =
MD5Encoder.encode(
md5Helper.digest(
(manager.getWebdavPassword(db, username) + ":" +
(String) params.get("nonce") + ":" +
getA2(context, params)).getBytes()));
//System.out.println("server digest : " + digest);
String nonce = (String) params.get("nonce");
if (digest.equals((String) params.get("response"))) {
status = manager.addUser(db, username, nonce);
}
}
} catch (SQLException e) {
e.printStackTrace(System.out);
//TODO: set error message in the response
} finally {
this.freeConnection(db, context);
}
}
return status;
}
/**
* Handles the special WebDAV methods.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
ActionContext context = new ActionContext(this, null, null, req, resp);
if (!systemInitialized(context)) {
if (System.getProperty("DEBUG") != null) {
System.out.println("WebdavServlet-> Initializing system");
}
initializeSystem(context);
}
// Authentication Check
String argHeader = req.getHeader("Authorization");
if (!allowUser(context, argHeader)) {
System.out.println("Unauthorized access");
if (context.getRequest().getHeader("user-agent").toLowerCase().startsWith("microsoft")
|| context.getRequest().getHeader("user-agent").toLowerCase().indexOf("windows") > -1) {
//Microsoft Client. Hence use Basic Authentication
//Basic Authentication
//System.out.println("Authentication Scheme-> BASIC");
resp.setHeader("WWW-Authenticate", "BASIC realm=\"" + USER_REALM + "\"");
} else {
//Any othe client use Digest Authentication
// Digest Authentication
// determine the 'number once' value that is unique for this auth
//System.out.println("Authentication Scheme-> DIGEST");
String nonce = generateNonce();
// determine the 'opaque' value which should be returned as is by the client
String opaque = generateOpaque();
resp.setHeader(
"WWW-Authenticate", "Digest realm=\"" + USER_REALM + "\", " +
"nonce=\"" + nonce + "\", " +
"opaque=\"" + opaque + "\"");
}
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
// Determine client action
String method = req.getMethod();
if (method.equals(METHOD_PROPFIND)) {
doPropfind(context);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(context);
} else if (method.equals(METHOD_PROPPATCH)) {
doProppatch(req, resp);
} else if (method.equals(METHOD_MKCOL)) {
doMkcol(req, resp);
} else if (method.equals(METHOD_COPY)) {
doCopy(req, resp);
} else if (method.equals(METHOD_MOVE)) {
doMove(req, resp);
} else if (method.equals(METHOD_LOCK)) {
doLock(req, resp);
} else if (method.equals(METHOD_UNLOCK)) {
doUnlock(req, resp);
} else {
// DefaultServlet processing
super.service(req, resp);
}
}
/**
* Description of the Method
*
* @param context Description of the Parameter
*/
protected void doOptions(ActionContext context) {
context.getResponse().setHeader("DAV", "1");
context.getResponse().setHeader("Allow", "GET, HEAD, POST, PUT, DELETE, OPTIONS, TRACE, PROPFIND");
}
/**
* Check if the conditions specified in the optional If headers are
* satisfied.
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param resourceInfo File object
* @return boolean true if the resource meets all the
* specified conditions, and false if any of the conditions is not
* satisfied, in which case request processing is stopped
* @throws java.io.IOException Description of the Exception
*/
protected boolean checkIfHeaders(HttpServletRequest request,
HttpServletResponse response,
ResourceInfo resourceInfo)
throws IOException {
if (!super.checkIfHeaders(request, response, resourceInfo)) {
return false;
}
// TODO : Checking the WebDAV If header
return true;
}
/**
* OPTIONS Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.addHeader("DAV", "1,2");
// Retrieve the resources
DirContext resources = getResources();
if (resources == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
StringBuffer methodsAllowed = determineMethodsAllowed(
resources,
req);
resp.addHeader("Allow", methodsAllowed.toString());
resp.addHeader("MS-Author-Via", "DAV");
}
/**
* PROPFIND Method.
*
* @param context Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doPropfind(ActionContext context)
throws ServletException, IOException {
String path = getRelativePath(context.getRequest());
//fix for windows clients
if (path.equals("/files")) {
path = "";
}
if (path.endsWith("/")) {
path = path.substring(0, path.length() - 1);
}
if ((path.toUpperCase().startsWith("/WEB-INF")) ||
(path.toUpperCase().startsWith("/META-INF"))) {
context.getResponse().sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
// Properties which are to be displayed.
Vector properties = null;
// Propfind depth
int depth = INFINITY;
// Propfind type
int type = FIND_ALL_PROP;
String depthStr = context.getRequest().getHeader("Depth");
if (depthStr == null) {
depth = INFINITY;
} else {
if (depthStr.equals("0")) {
depth = 0;
} else if (depthStr.equals("1")) {
depth = 1;
} else if (depthStr.equals("infinity")) {
depth = INFINITY;
}
}
/*
* Read the request xml and determine all the properties
*/
/*
Node propNode = null;
DocumentBuilder documentBuilder = getDocumentBuilder();
try {
Document document = documentBuilder.parse
(new InputSource(context.getRequest().getInputStream()));
// Get the root element of the document
Element rootElement = document.getDocumentElement();
NodeList childList = rootElement.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
switch (currentNode.getNodeType()) {
case Node.TEXT_NODE:
break;
case Node.ELEMENT_NODE:
if (currentNode.getNodeName().endsWith("prop")) {
type = FIND_BY_PROPERTY;
propNode = currentNode;
}
if (currentNode.getNodeName().endsWith("propname")) {
type = FIND_PROPERTY_NAMES;
}
if (currentNode.getNodeName().endsWith("allprop")) {
type = FIND_ALL_PROP;
}
break;
}
}
} catch (Exception e) {
// Most likely there was no content : we use the defaults.
// TODO : Enhance that !
e.printStackTrace(System.out);
}
if (type == FIND_BY_PROPERTY) {
properties = new Vector();
NodeList childList = propNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
switch (currentNode.getNodeType()) {
case Node.TEXT_NODE:
break;
case Node.ELEMENT_NODE:
String nodeName = currentNode.getNodeName();
String propertyName = null;
if (nodeName.indexOf(':') != -1) {
propertyName = nodeName.substring
(nodeName.indexOf(':') + 1);
} else {
propertyName = nodeName;
}
// href is a live property which is handled differently
properties.addElement(propertyName);
break;
}
}
}
*/
// Properties have been determined
// Retrieve the resources
Connection db = null;
boolean exists = true;
boolean status = true;
Object object = null;
ModuleContext resources = null;
StringBuffer xmlsb = new StringBuffer();
try {
db = this.getConnection(context);
resources = getCFSResources(db, context);
if (resources == null) {
context.getResponse().sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
object = resources.lookup(db, path);
} catch (NamingException e) {
//e.printStackTrace(System.out);
exists = false;
int slash = path.lastIndexOf('/');
if (slash != -1) {
String parentPath = path.substring(0, slash);
Vector currentLockNullResources =
(Vector) lockNullResources.get(parentPath);
if (currentLockNullResources != null) {
Enumeration lockNullResourcesList =
currentLockNullResources.elements();
while (lockNullResourcesList.hasMoreElements()) {
String lockNullPath = (String)
lockNullResourcesList.nextElement();
if (lockNullPath.equals(path)) {
context.getResponse().setStatus(WebdavStatus.SC_MULTI_STATUS);
context.getResponse().setContentType("text/xml; charset=UTF-8");
// Create multistatus object
XMLWriter generatedXML =
new XMLWriter(context.getResponse().getWriter());
generatedXML.writeXMLHeader();
generatedXML.writeElement
(
null, "multistatus"
+ generateNamespaceDeclarations(),
XMLWriter.OPENING);
parseLockNullProperties
(
context.getRequest(), generatedXML, lockNullPath, type,
properties);
generatedXML.writeElement(
null, "multistatus",
XMLWriter.CLOSING);
generatedXML.sendData();
//e.printStackTrace(System.out);
return;
}
}
}
}
} catch (SQLException e) {
e.printStackTrace(System.out);
context.getResponse().sendError(SQLERROR, e.getMessage());
status = false;
} finally {
this.freeConnection(db, context);
}
if (!status) {
return;
}
if (!exists) {
context.getResponse().sendError(HttpServletResponse.SC_NOT_FOUND, path);
return;
}
context.getResponse().setStatus(WebdavStatus.SC_MULTI_STATUS);
context.getResponse().setContentType("text/xml; charset=UTF-8");
// Create multistatus object
////System.out.println("Creating Multistatus Object");
XMLWriter generatedXML = new XMLWriter(context.getResponse().getWriter());
generatedXML.writeXMLHeader();
generatedXML.writeElement(
null, "multistatus"
+ generateNamespaceDeclarations(),
XMLWriter.OPENING);
//System.out.println("Depth: " + depth);
if (depth == 0) {
parseProperties(context, resources, generatedXML, path, type, properties);
} else {
// The stack always contains the object of the current level
Stack stack = new Stack();
stack.push(path);
// Stack of the objects one level below
Stack stackBelow = new Stack();
while ((!stack.isEmpty()) && (depth >= 0)) {
String currentPath = (String) stack.pop();
if (!currentPath.equals(path)) {
parseProperties(context, resources, generatedXML, currentPath, type, properties);
}
try {
db = this.getConnection(context);
object = resources.lookup(db, currentPath);
} catch (NamingException e) {
continue;
} catch (SQLException e) {
//e.printStackTrace(System.out);
context.getResponse().sendError(SQLERROR, e.getMessage());
status = false;
} finally {
this.freeConnection(db, context);
}
if (!status) {
return;
}
if ((object instanceof ModuleContext) && depth > 0) {
// Get a list of all the resources at the current path and store them
// in the stack
try {
NamingEnumeration enum1 = resources.list(currentPath);
int count = 0;
while (enum1.hasMoreElements()) {
NameClassPair ncPair =
(NameClassPair) enum1.nextElement();
String newPath = currentPath;
if (!(newPath.endsWith("/"))) {
newPath += "/";
}
newPath += ncPair.getName();
stackBelow.push(newPath);
count++;
}
if (currentPath.equals(path) && count == 0) {
// This directory does not have any files or folders.
parseProperties(context, resources, generatedXML, properties);
}
} catch (NamingException e) {
//e.printStackTrace(System.out);
context.getResponse().sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
return;
}
// Displaying the lock-null resources present in that collection
String lockPath = currentPath;
if (lockPath.endsWith("/")) {
lockPath =
lockPath.substring(0, lockPath.length() - 1);
}
Vector currentLockNullResources =
(Vector) lockNullResources.get(lockPath);
if (currentLockNullResources != null) {
Enumeration lockNullResourcesList =
currentLockNullResources.elements();
while (lockNullResourcesList.hasMoreElements()) {
String lockNullPath = (String)
lockNullResourcesList.nextElement();
System.out.println("Lock null path: " + lockNullPath);
parseLockNullProperties
(
context.getRequest(), generatedXML, lockNullPath, type,
properties);
}
}
}
if (stack.isEmpty()) {
depth--;
stack = stackBelow;
stackBelow = new Stack();
}
xmlsb.append(generatedXML.toString());
//System.out.println("xml : " + generatedXML.toString());
generatedXML.sendData();
}
}
generatedXML.writeElement(
null, "multistatus",
XMLWriter.CLOSING);
xmlsb.append(generatedXML.toString());
generatedXML.sendData();
//System.out.println("xml: " + xmlsb.toString());
}
/**
* PROPPATCH Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doProppatch(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
}
/**
* MKCOL Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doMkcol(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
String path = getRelativePath(req);
if ((path.toUpperCase().startsWith("/WEB-INF")) ||
(path.toUpperCase().startsWith("/META-INF"))) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
// Retrieve the resources
DirContext resources = getResources();
if (resources == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
boolean exists = true;
Object object = null;
try {
object = resources.lookup(path);
} catch (NamingException e) {
exists = false;
}
// Can't create a collection if a resource already exists at the given
// path
if (exists) {
// Get allowed methods
StringBuffer methodsAllowed = determineMethodsAllowed(
resources,
req);
resp.addHeader("Allow", methodsAllowed.toString());
resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
return;
}
if (req.getInputStream().available() > 0) {
DocumentBuilder documentBuilder = getDocumentBuilder();
try {
Document document = documentBuilder.parse
(new InputSource(req.getInputStream()));
// TODO : Process this request body
resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
return;
} catch (SAXException saxe) {
// Parse error - assume invalid content
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
return;
}
}
boolean result = true;
try {
resources.createSubcontext(path);
} catch (NamingException e) {
result = false;
}
if (!result) {
resp.sendError(
WebdavStatus.SC_CONFLICT,
WebdavStatus.getStatusText
(WebdavStatus.SC_CONFLICT));
} else {
resp.setStatus(WebdavStatus.SC_CREATED);
// Removing any lock-null resource which would be present
lockNullResources.remove(path);
}
}
/**
* DELETE Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
deleteResource(req, resp);
}
/**
* Process a POST request for the specified resource.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws java.io.IOException if an input/output error occurs
* @throws javax.servlet.ServletException if a servlet-specified error occurs
*/
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
super.doPut(req, resp);
String path = getRelativePath(req);
// Removing any lock-null resource which would be present
lockNullResources.remove(path);
}
/**
* COPY Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doCopy(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
copyResource(req, resp);
}
/**
* MOVE Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doMove(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
String path = getRelativePath(req);
if (copyResource(req, resp)) {
deleteResource(path, req, resp, false);
}
}
/**
* LOCK Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doLock(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
WebdavServlet.LockInfo lock = new WebdavServlet.LockInfo();
// Parsing lock request
// Parsing depth header
String depthStr = req.getHeader("Depth");
if (depthStr == null) {
lock.depth = INFINITY;
} else {
if (depthStr.equals("0")) {
lock.depth = 0;
} else {
lock.depth = INFINITY;
}
}
// Parsing timeout header
int lockDuration = DEFAULT_TIMEOUT;
String lockDurationStr = req.getHeader("Timeout");
if (lockDurationStr == null) {
lockDuration = DEFAULT_TIMEOUT;
} else {
int commaPos = lockDurationStr.indexOf(",");
// If multiple timeouts, just use the first
if (commaPos != -1) {
lockDurationStr = lockDurationStr.substring(0, commaPos);
}
if (lockDurationStr.startsWith("Second-")) {
lockDuration =
(new Integer(lockDurationStr.substring(7))).intValue();
} else {
if (lockDurationStr.equalsIgnoreCase("infinity")) {
lockDuration = MAX_TIMEOUT;
} else {
try {
lockDuration =
(new Integer(lockDurationStr)).intValue();
} catch (NumberFormatException e) {
lockDuration = MAX_TIMEOUT;
}
}
}
if (lockDuration == 0) {
lockDuration = DEFAULT_TIMEOUT;
}
if (lockDuration > MAX_TIMEOUT) {
lockDuration = MAX_TIMEOUT;
}
}
lock.expiresAt = System.currentTimeMillis() + (lockDuration * 1000);
int lockRequestType = LOCK_CREATION;
Node lockInfoNode = null;
DocumentBuilder documentBuilder = getDocumentBuilder();
try {
Document document = documentBuilder.parse(
new InputSource
(req.getInputStream()));
// Get the root element of the document
Element rootElement = document.getDocumentElement();
lockInfoNode = rootElement;
} catch (Exception e) {
lockRequestType = LOCK_REFRESH;
}
if (lockInfoNode != null) {
// Reading lock information
NodeList childList = lockInfoNode.getChildNodes();
StringWriter strWriter = null;
DOMWriter domWriter = null;
Node lockScopeNode = null;
Node lockTypeNode = null;
Node lockOwnerNode = null;
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
switch (currentNode.getNodeType()) {
case Node.TEXT_NODE:
break;
case Node.ELEMENT_NODE:
String nodeName = currentNode.getNodeName();
if (nodeName.endsWith("lockscope")) {
lockScopeNode = currentNode;
}
if (nodeName.endsWith("locktype")) {
lockTypeNode = currentNode;
}
if (nodeName.endsWith("owner")) {
lockOwnerNode = currentNode;
}
break;
}
}
if (lockScopeNode != null) {
childList = lockScopeNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
switch (currentNode.getNodeType()) {
case Node.TEXT_NODE:
break;
case Node.ELEMENT_NODE:
String tempScope = currentNode.getNodeName();
if (tempScope.indexOf(':') != -1) {
lock.scope = tempScope.substring
(tempScope.indexOf(':') + 1);
} else {
lock.scope = tempScope;
}
break;
}
}
if (lock.scope == null) {
// Bad request
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
}
} else {
// Bad request
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
}
if (lockTypeNode != null) {
childList = lockTypeNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
switch (currentNode.getNodeType()) {
case Node.TEXT_NODE:
break;
case Node.ELEMENT_NODE:
String tempType = currentNode.getNodeName();
if (tempType.indexOf(':') != -1) {
lock.type =
tempType.substring(tempType.indexOf(':') + 1);
} else {
lock.type = tempType;
}
break;
}
}
if (lock.type == null) {
// Bad request
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
}
} else {
// Bad request
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
}
if (lockOwnerNode != null) {
childList = lockOwnerNode.getChildNodes();
for (int i = 0; i < childList.getLength(); i++) {
Node currentNode = childList.item(i);
switch (currentNode.getNodeType()) {
case Node.TEXT_NODE:
lock.owner += currentNode.getNodeValue();
break;
case Node.ELEMENT_NODE:
strWriter = new StringWriter();
domWriter = new DOMWriter(strWriter, true);
domWriter.setQualifiedNames(false);
domWriter.print(currentNode);
lock.owner += strWriter.toString();
break;
}
}
if (lock.owner == null) {
// Bad request
resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
}
} else {
lock.owner = new String();
}
}
String path = getRelativePath(req);
lock.path = path;
// Retrieve the resources
DirContext resources = getResources();
if (resources == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
boolean exists = true;
Object object = null;
try {
object = resources.lookup(path);
} catch (NamingException e) {
exists = false;
}
Enumeration locksList = null;
if (lockRequestType == LOCK_CREATION) {
// Generating lock id
String lockTokenStr = req.getServletPath() + "-" + lock.type + "-"
+ lock.scope + "-" + req.getUserPrincipal() + "-"
+ lock.depth + "-" + lock.owner + "-" + lock.tokens + "-"
+ lock.expiresAt + "-" + System.currentTimeMillis() + "-"
+ secret;
String lockToken =
md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
if ((exists) && (object instanceof DirContext) &&
(lock.depth == INFINITY)) {
// Locking a collection (and all its member resources)
// Checking if a child resource of this collection is
// already locked
Vector lockPaths = new Vector();
locksList = collectionLocks.elements();
while (locksList.hasMoreElements()) {
WebdavServlet.LockInfo currentLock = (WebdavServlet.LockInfo) locksList.nextElement();
if (currentLock.hasExpired()) {
resourceLocks.remove(currentLock.path);
continue;
}
if ((currentLock.path.startsWith(lock.path)) &&
((currentLock.isExclusive()) ||
(lock.isExclusive()))) {
// A child collection of this collection is locked
lockPaths.addElement(currentLock.path);
}
}
locksList = resourceLocks.elements();
while (locksList.hasMoreElements()) {
WebdavServlet.LockInfo currentLock = (WebdavServlet.LockInfo) locksList.nextElement();
if (currentLock.hasExpired()) {
resourceLocks.remove(currentLock.path);
continue;
}
if ((currentLock.path.startsWith(lock.path)) &&
((currentLock.isExclusive()) ||
(lock.isExclusive()))) {
// A child resource of this collection is locked
lockPaths.addElement(currentLock.path);
}
}
if (!lockPaths.isEmpty()) {
// One of the child paths was locked
// We generate a multistatus error report
Enumeration lockPathsList = lockPaths.elements();
resp.setStatus(WebdavStatus.SC_CONFLICT);
XMLWriter generatedXML = new XMLWriter();
generatedXML.writeXMLHeader();
generatedXML.writeElement
(
null, "multistatus" + generateNamespaceDeclarations(),
XMLWriter.OPENING);
while (lockPathsList.hasMoreElements()) {
generatedXML.writeElement(
null, "response",
XMLWriter.OPENING);
generatedXML.writeElement(
null, "href",
XMLWriter.OPENING);
generatedXML.writeText((String) lockPathsList.nextElement());
generatedXML.writeElement(
null, "href",
XMLWriter.CLOSING);
generatedXML.writeElement(
null, "status",
XMLWriter.OPENING);
generatedXML.writeText(
"HTTP/1.1 " + WebdavStatus.SC_LOCKED
+ " " + WebdavStatus.getStatusText(WebdavStatus.SC_LOCKED));
generatedXML.writeElement(
null, "status",
XMLWriter.CLOSING);
generatedXML.writeElement(
null, "response",
XMLWriter.CLOSING);
}
generatedXML.writeElement(
null, "multistatus",
XMLWriter.CLOSING);
Writer writer = resp.getWriter();
writer.write(generatedXML.toString());
writer.close();
return;
}
boolean addLock = true;
// Checking if there is already a shared lock on this path
locksList = collectionLocks.elements();
while (locksList.hasMoreElements()) {
WebdavServlet.LockInfo currentLock = (WebdavServlet.LockInfo) locksList.nextElement();
if (currentLock.path.equals(lock.path)) {
if (currentLock.isExclusive()) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
} else {
if (lock.isExclusive()) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
}
currentLock.tokens.addElement(lockToken);
lock = currentLock;
addLock = false;
}
}
if (addLock) {
lock.tokens.addElement(lockToken);
collectionLocks.addElement(lock);
}
} else {
// Locking a single resource
// Retrieving an already existing lock on that resource
WebdavServlet.LockInfo presentLock = (WebdavServlet.LockInfo) resourceLocks.get(lock.path);
if (presentLock != null) {
if ((presentLock.isExclusive()) || (lock.isExclusive())) {
// If either lock is exclusive, the lock can't be
// granted
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
return;
} else {
presentLock.tokens.addElement(lockToken);
lock = presentLock;
}
} else {
lock.tokens.addElement(lockToken);
resourceLocks.put(lock.path, lock);
// Checking if a resource exists at this path
exists = true;
try {
object = resources.lookup(path);
} catch (NamingException e) {
exists = false;
}
if (!exists) {
// "Creating" a lock-null resource
int slash = lock.path.lastIndexOf('/');
String parentPath = lock.path.substring(0, slash);
Vector lockNulls =
(Vector) lockNullResources.get(parentPath);
if (lockNulls == null) {
lockNulls = new Vector();
lockNullResources.put(parentPath, lockNulls);
}
lockNulls.addElement(lock.path);
}
// Add the Lock-Token header as by RFC 2518 8.10.1
// - only do this for newly created locks
resp.addHeader(
"Lock-Token", "<opaquelocktoken:"
+ lockToken + ">");
}
}
}
if (lockRequestType == LOCK_REFRESH) {
String ifHeader = req.getHeader("If");
if (ifHeader == null) {
ifHeader = "";
}
// Checking resource locks
WebdavServlet.LockInfo toRenew = (WebdavServlet.LockInfo) resourceLocks.get(path);
Enumeration tokenList = null;
if (lock != null) {
// At least one of the tokens of the locks must have been given
tokenList = toRenew.tokens.elements();
while (tokenList.hasMoreElements()) {
String token = (String) tokenList.nextElement();
if (ifHeader.indexOf(token) != -1) {
toRenew.expiresAt = lock.expiresAt;
lock = toRenew;
}
}
}
// Checking inheritable collection locks
Enumeration collectionLocksList = collectionLocks.elements();
while (collectionLocksList.hasMoreElements()) {
toRenew = (WebdavServlet.LockInfo) collectionLocksList.nextElement();
if (path.equals(toRenew.path)) {
tokenList = toRenew.tokens.elements();
while (tokenList.hasMoreElements()) {
String token = (String) tokenList.nextElement();
if (ifHeader.indexOf(token) != -1) {
toRenew.expiresAt = lock.expiresAt;
lock = toRenew;
}
}
}
}
}
// Set the status, then generate the XML response containing
// the lock information
XMLWriter generatedXML = new XMLWriter();
generatedXML.writeXMLHeader();
generatedXML.writeElement(
null, "prop"
+ generateNamespaceDeclarations(),
XMLWriter.OPENING);
generatedXML.writeElement(
null, "lockdiscovery",
XMLWriter.OPENING);
lock.toXML(generatedXML);
generatedXML.writeElement(
null, "lockdiscovery",
XMLWriter.CLOSING);
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
resp.setStatus(WebdavStatus.SC_OK);
resp.setContentType("text/xml; charset=UTF-8");
Writer writer = resp.getWriter();
writer.write(generatedXML.toString());
writer.close();
}
/**
* UNLOCK Method.
*
* @param req Description of the Parameter
* @param resp Description of the Parameter
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
protected void doUnlock(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (readOnly) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return;
}
if (isLocked(req)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return;
}
String path = getRelativePath(req);
String lockTokenHeader = req.getHeader("Lock-Token");
if (lockTokenHeader == null) {
lockTokenHeader = "";
}
// Checking resource locks
WebdavServlet.LockInfo lock = (WebdavServlet.LockInfo) resourceLocks.get(path);
Enumeration tokenList = null;
if (lock != null) {
// At least one of the tokens of the locks must have been given
tokenList = lock.tokens.elements();
while (tokenList.hasMoreElements()) {
String token = (String) tokenList.nextElement();
if (lockTokenHeader.indexOf(token) != -1) {
lock.tokens.removeElement(token);
}
}
if (lock.tokens.isEmpty()) {
resourceLocks.remove(path);
// Removing any lock-null resource which would be present
lockNullResources.remove(path);
}
}
// Checking inheritable collection locks
Enumeration collectionLocksList = collectionLocks.elements();
while (collectionLocksList.hasMoreElements()) {
lock = (WebdavServlet.LockInfo) collectionLocksList.nextElement();
if (path.equals(lock.path)) {
tokenList = lock.tokens.elements();
while (tokenList.hasMoreElements()) {
String token = (String) tokenList.nextElement();
if (lockTokenHeader.indexOf(token) != -1) {
lock.tokens.removeElement(token);
break;
}
}
if (lock.tokens.isEmpty()) {
collectionLocks.removeElement(lock);
// Removing any lock-null resource which would be present
lockNullResources.remove(path);
}
}
}
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
}
// -------------------------------------------------------- Private Methods
/**
* Generate the namespace declarations.
*
* @return Description of the Return Value
*/
private String generateNamespaceDeclarations() {
return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
}
/**
* 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.
*
* @param req Servlet request
* @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).
*/
private boolean isLocked(HttpServletRequest req) {
String path = getRelativePath(req);
String ifHeader = req.getHeader("If");
if (ifHeader == null) {
ifHeader = "";
}
String lockTokenHeader = req.getHeader("Lock-Token");
if (lockTokenHeader == null) {
lockTokenHeader = "";
}
return isLocked(path, ifHeader + lockTokenHeader);
}
/**
* Check to see if a resource is currently write locked.
*
* @param path Path of the resource
* @param ifHeader "If" HTTP header which was included in the request
* @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).
*/
private boolean isLocked(String path, String ifHeader) {
// Checking resource locks
WebdavServlet.LockInfo lock = (WebdavServlet.LockInfo) resourceLocks.get(path);
Enumeration tokenList = null;
if ((lock != null) && (lock.hasExpired())) {
resourceLocks.remove(path);
} else if (lock != null) {
// At least one of the tokens of the locks must have been given
tokenList = lock.tokens.elements();
boolean tokenMatch = false;
while (tokenList.hasMoreElements()) {
String token = (String) tokenList.nextElement();
if (ifHeader.indexOf(token) != -1) {
tokenMatch = true;
}
}
if (!tokenMatch) {
return true;
}
}
// Checking inheritable collection locks
Enumeration collectionLocksList = collectionLocks.elements();
while (collectionLocksList.hasMoreElements()) {
lock = (WebdavServlet.LockInfo) collectionLocksList.nextElement();
if (lock.hasExpired()) {
collectionLocks.removeElement(lock);
} else if (path.startsWith(lock.path)) {
tokenList = lock.tokens.elements();
boolean tokenMatch = false;
while (tokenList.hasMoreElements()) {
String token = (String) tokenList.nextElement();
if (ifHeader.indexOf(token) != -1) {
tokenMatch = true;
}
}
if (!tokenMatch) {
return true;
}
}
}
return false;
}
/**
* Copy a resource.
*
* @param req Servlet request
* @param resp Servlet response
* @return boolean true if the copy is successful
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
private boolean copyResource(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
// Parsing destination header
String destinationPath = req.getHeader("Destination");
if (destinationPath == null) {
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
return false;
}
// Remove url encoding from destination
destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
int protocolIndex = destinationPath.indexOf("://");
if (protocolIndex >= 0) {
// if the Destination URL contains the protocol, we can safely
// trim everything upto the first "/" character after "://"
int firstSeparator =
destinationPath.indexOf("/", protocolIndex + 4);
if (firstSeparator < 0) {
destinationPath = "/";
} else {
destinationPath = destinationPath.substring(firstSeparator);
}
} else {
String hostName = req.getServerName();
if ((hostName != null) && (destinationPath.startsWith(hostName))) {
destinationPath = destinationPath.substring(hostName.length());
}
int portIndex = destinationPath.indexOf(":");
if (portIndex >= 0) {
destinationPath = destinationPath.substring(portIndex);
}
if (destinationPath.startsWith(":")) {
int firstSeparator = destinationPath.indexOf("/");
if (firstSeparator < 0) {
destinationPath = "/";
} else {
destinationPath =
destinationPath.substring(firstSeparator);
}
}
}
// Normalise destination path (remove '.' and '..')
destinationPath = normalize(destinationPath);
String contextPath = req.getContextPath();
if ((contextPath != null) &&
(destinationPath.startsWith(contextPath))) {
destinationPath = destinationPath.substring(contextPath.length());
}
String pathInfo = req.getPathInfo();
if (pathInfo != null) {
String servletPath = req.getServletPath();
if ((servletPath != null) &&
(destinationPath.startsWith(servletPath))) {
destinationPath = destinationPath.substring(servletPath.length());
}
}
if (debug > 0) {
System.out.println("Dest path :" + destinationPath);
}
if ((destinationPath.toUpperCase().startsWith("/WEB-INF")) ||
(destinationPath.toUpperCase().startsWith("/META-INF"))) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return false;
}
String path = getRelativePath(req);
if ((path.toUpperCase().startsWith("/WEB-INF")) ||
(path.toUpperCase().startsWith("/META-INF"))) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return false;
}
if (destinationPath.equals(path)) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return false;
}
// Parsing overwrite header
boolean overwrite = true;
String overwriteHeader = req.getHeader("Overwrite");
if (overwriteHeader != null) {
if (overwriteHeader.equalsIgnoreCase("T")) {
overwrite = true;
} else {
overwrite = false;
}
}
// Overwriting the destination
// Retrieve the resources
DirContext resources = getResources();
if (resources == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;
}
boolean exists = true;
try {
resources.lookup(destinationPath);
} catch (NamingException e) {
exists = false;
}
if (overwrite) {
// Delete destination resource, if it exists
if (exists) {
if (!deleteResource(destinationPath, req, resp, true)) {
return false;
}
} else {
resp.setStatus(WebdavStatus.SC_CREATED);
}
} else {
// If the destination exists, then it's a conflict
if (exists) {
resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
return false;
}
}
// Copying source to destination
Hashtable errorList = new Hashtable();
boolean result = copyResource(
resources, errorList,
path, destinationPath);
if ((!result) || (!errorList.isEmpty())) {
sendReport(req, resp, errorList);
return false;
}
// Removing any lock-null resource which would be present at
// the destination path
lockNullResources.remove(destinationPath);
return true;
}
/**
* Copy a collection.
*
* @param resources Resources implementation to be used
* @param errorList Hashtable containing the list of errors which occurred
* during the copy operation
* @param source Path of the resource to be copied
* @param dest Destination path
* @return Description of the Return Value
*/
private boolean copyResource(DirContext resources, Hashtable errorList,
String source, String dest) {
if (debug > 1) {
System.out.println("Copy: " + source + " To: " + dest);
}
Object object = null;
try {
object = resources.lookup(source);
} catch (NamingException e) {
}
if (object instanceof DirContext) {
try {
resources.createSubcontext(dest);
} catch (NamingException e) {
errorList.put
(dest, new Integer(WebdavStatus.SC_CONFLICT));
return false;
}
try {
NamingEnumeration enum1 = resources.list(source);
while (enum1.hasMoreElements()) {
NameClassPair ncPair = (NameClassPair) enum1.nextElement();
String childDest = dest;
if (!childDest.equals("/")) {
childDest += "/";
}
childDest += ncPair.getName();
String childSrc = source;
if (!childSrc.equals("/")) {
childSrc += "/";
}
childSrc += ncPair.getName();
copyResource(resources, errorList, childSrc, childDest);
}
} catch (NamingException e) {
errorList.put
(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
return false;
}
} else {
if (object instanceof Resource) {
try {
resources.bind(dest, object);
} catch (NamingException e) {
errorList.put
(
source,
new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
return false;
}
} else {
errorList.put
(
source,
new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
return false;
}
}
return true;
}
/**
* Delete a resource.
*
* @param req Servlet request
* @param resp Servlet response
* @return boolean true if the copy is successful
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
private boolean deleteResource(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
String path = getRelativePath(req);
return deleteResource(path, req, resp, true);
}
/**
* Delete a resource.
*
* @param path Path of the resource which is to be deleted
* @param req Servlet request
* @param resp Servlet response
* @param setStatus Should the response status be set on
* successful completion
* @return Description of the Return Value
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
private boolean deleteResource(String path, HttpServletRequest req,
HttpServletResponse resp, boolean setStatus)
throws ServletException, IOException {
if ((path.toUpperCase().startsWith("/WEB-INF")) ||
(path.toUpperCase().startsWith("/META-INF"))) {
resp.sendError(WebdavStatus.SC_FORBIDDEN);
return false;
}
String ifHeader = req.getHeader("If");
if (ifHeader == null) {
ifHeader = "";
}
String lockTokenHeader = req.getHeader("Lock-Token");
if (lockTokenHeader == null) {
lockTokenHeader = "";
}
if (isLocked(path, ifHeader + lockTokenHeader)) {
resp.sendError(WebdavStatus.SC_LOCKED);
return false;
}
// Retrieve the resources
DirContext resources = getResources();
if (resources == null) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return false;
}
boolean exists = true;
Object object = null;
try {
object = resources.lookup(path);
} catch (NamingException e) {
exists = false;
}
if (!exists) {
resp.sendError(WebdavStatus.SC_NOT_FOUND);
return false;
}
boolean collection = (object instanceof DirContext);
if (!collection) {
try {
resources.unbind(path);
} catch (NamingException e) {
resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
return false;
}
} else {
Hashtable errorList = new Hashtable();
deleteCollection(req, resources, path, errorList);
try {
resources.unbind(path);
} catch (NamingException e) {
errorList.put(
path, new Integer
(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
}
if (!errorList.isEmpty()) {
sendReport(req, resp, errorList);
return false;
}
}
if (setStatus) {
resp.setStatus(WebdavStatus.SC_NO_CONTENT);
}
return true;
}
/**
* Deletes a collection.
*
* @param resources Resources implementation associated with the context
* @param path Path to the collection to be deleted
* @param errorList Contains the list of the errors which occurred
* @param req Description of the Parameter
*/
private void deleteCollection(HttpServletRequest req,
DirContext resources,
String path, Hashtable errorList) {
if (debug > 1) {
System.out.println("Delete:" + path);
}
if ((path.toUpperCase().startsWith("/WEB-INF")) ||
(path.toUpperCase().startsWith("/META-INF"))) {
errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
return;
}
String ifHeader = req.getHeader("If");
if (ifHeader == null) {
ifHeader = "";
}
String lockTokenHeader = req.getHeader("Lock-Token");
if (lockTokenHeader == null) {
lockTokenHeader = "";
}
Enumeration enum1 = null;
try {
enum1 = resources.list(path);
} catch (NamingException e) {
errorList.put(
path, new Integer
(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
return;
}
while (enum1.hasMoreElements()) {
NameClassPair ncPair = (NameClassPair) enum1.nextElement();
String childName = path;
if (!childName.equals("/")) {
childName += "/";
}
childName += ncPair.getName();
if (isLocked(childName, ifHeader + lockTokenHeader)) {
errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
} else {
try {
Object object = resources.lookup(childName);
if (object instanceof DirContext) {
deleteCollection(req, resources, childName, errorList);
}
try {
resources.unbind(childName);
} catch (NamingException e) {
if (!(object instanceof DirContext)) {
// If it's not a collection, then it's an unknown
// error
errorList.put
(
childName, new Integer
(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
}
}
} catch (NamingException e) {
errorList.put
(
childName, new Integer
(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
}
}
}
}
/**
* Send a multistatus element containing a complete error report to the
* client.
*
* @param req Servlet request
* @param resp Servlet response
* @param errorList List of error to be displayed
* @throws javax.servlet.ServletException Description of the Exception
* @throws java.io.IOException Description of the Exception
*/
private void sendReport(HttpServletRequest req, HttpServletResponse resp,
Hashtable errorList)
throws ServletException, IOException {
resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
String absoluteUri = req.getRequestURI();
String relativePath = getRelativePath(req);
XMLWriter generatedXML = new XMLWriter();
generatedXML.writeXMLHeader();
generatedXML.writeElement(
null, "multistatus"
+ generateNamespaceDeclarations(),
XMLWriter.OPENING);
Enumeration pathList = errorList.keys();
while (pathList.hasMoreElements()) {
String errorPath = (String) pathList.nextElement();
int errorCode = ((Integer) errorList.get(errorPath)).intValue();
generatedXML.writeElement(null, "response", XMLWriter.OPENING);
generatedXML.writeElement(null, "href", XMLWriter.OPENING);
String toAppend = errorPath.substring(relativePath.length());
if (!toAppend.startsWith("/")) {
toAppend = "/" + toAppend;
}
generatedXML.writeText(absoluteUri + toAppend);
generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(
"HTTP/1.1 " + errorCode + " "
+ WebdavStatus.getStatusText(errorCode));
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
}
generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
Writer writer = resp.getWriter();
writer.write(generatedXML.toString());
writer.close();
}
/**
* Propfind helper method.
*
* @param resources Resources object associated with this context
* @param generatedXML XML response to the Propfind request
* @param path Path of the current resource
* @param type Propfind type
* @param propertiesVector If the propfind type is find properties by name,
* then this Vector contains those properties
* @param context Description of the Parameter
* @throws java.io.IOException Description of the Exception
*/
private void parseProperties(ActionContext context, ModuleContext resources,
XMLWriter generatedXML,
String path, int type,
Vector propertiesVector) throws IOException {
// Exclude any resource in the /WEB-INF and /META-INF subdirectories
// (the "toUpperCase()" avoids problems on Windows systems)
if (path.toUpperCase().startsWith("/WEB-INF") ||
path.toUpperCase().startsWith("/META-INF")) {
return;
}
Connection db = null;
ResourceInfo resourceInfo = null;
boolean actionStatus = true;
try {
db = this.getConnection(context);
resourceInfo = new ResourceInfo(db, path, resources);
} catch (SQLException e) {
e.printStackTrace(System.out);
context.getResponse().sendError(SQLERROR, e.getMessage());
actionStatus = false;
} catch (FileNotFoundException e) {
e.printStackTrace(System.out);
actionStatus = false;
} finally {
this.freeConnection(db, context);
}
if (!actionStatus) {
return;
}
generatedXML.writeElement(null, "response", XMLWriter.OPENING);
String status = new String(
"HTTP/1.1 " + WebdavStatus.SC_OK + " "
+ WebdavStatus.getStatusText
(WebdavStatus.SC_OK));
// Generating href element
generatedXML.writeElement(null, "href", XMLWriter.OPENING);
String href = context.getRequest().getContextPath();
if ((href.endsWith("/")) && (path.startsWith("/"))) {
href += path.substring(1);
} else {
href += path;
}
if ((resourceInfo.collection) && (!href.endsWith("/"))) {
href += "/";
}
generatedXML.writeText(rewriteUrl(href));
generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
String resourceName = path;
int lastSlash = path.lastIndexOf('/');
if (lastSlash != -1) {
resourceName = resourceName.substring(lastSlash + 1);
}
switch (type) {
case FIND_ALL_PROP:
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
generatedXML.writeProperty(null, "creationdate", getISOCreationDate(resourceInfo.creationDate));
generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
generatedXML.writeData(resourceName);
generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
if (!resourceInfo.collection) {
generatedXML.writeProperty(null, "getlastmodified", resourceInfo.httpDate);
generatedXML.writeProperty(null, "getcontentlength", String.valueOf(resourceInfo.length));
String contentType = getServletContext().getMimeType(resourceInfo.path);
if (contentType != null) {
generatedXML.writeProperty(null, "getcontenttype", contentType);
}
generatedXML.writeProperty(null, "getetag", getETag(resourceInfo));
generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
} else {
generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
}
generatedXML.writeProperty(null, "source", "");
String supportedLocks = "<lockentry>"
+ "<lockscope><exclusive/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>" + "<lockentry>"
+ "<lockscope><shared/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>";
generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
generatedXML.writeText(supportedLocks);
generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
generateLockDiscovery(path, generatedXML);
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
break;
case FIND_PROPERTY_NAMES:
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
if (!resourceInfo.collection) {
generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT);
}
generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
break;
case FIND_BY_PROPERTY:
Vector propertiesNotFound = new Vector();
// Parse the list of properties
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
Enumeration properties = propertiesVector.elements();
while (properties.hasMoreElements()) {
String property = (String) properties.nextElement();
if (property.equals("creationdate")) {
generatedXML.writeProperty(null, "creationdate", getISOCreationDate(resourceInfo.creationDate));
} else if (property.equals("displayname")) {
generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
generatedXML.writeData(resourceName);
generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
} else if (property.equals("getcontentlanguage")) {
if (resourceInfo.collection) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
}
} else if (property.equals("getcontentlength")) {
if (resourceInfo.collection) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeProperty(null, "getcontentlength", (String.valueOf(resourceInfo.length)));
}
} else if (property.equals("getcontenttype")) {
if (resourceInfo.collection) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeProperty(null, "getcontenttype", getServletContext().getMimeType(resourceInfo.path));
}
} else if (property.equals("getetag")) {
if (resourceInfo.collection) {
propertiesNotFound.addElement(property);
} else {
generatedXML.writeProperty(null, "getetag", getETag(resourceInfo));
}
} else if (property.equals("getlastmodified")) {
if (resourceInfo.collection) {
//propertiesNotFound.addElement(property);
// change
generatedXML.writeProperty(null, "getlastmodified", resourceInfo.httpDate);
} else {
generatedXML.writeProperty(null, "getlastmodified", resourceInfo.httpDate);
}
} else if (property.equals("resourcetype")) {
if (resourceInfo.collection) {
generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
} else {
generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
}
} else if (property.equals("source")) {
generatedXML.writeProperty(null, "source", "");
} else if (property.equals("supportedlock")) {
supportedLocks = "<lockentry>"
+ "<lockscope><exclusive/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>" + "<lockentry>"
+ "<lockscope><shared/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>";
generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
generatedXML.writeText(supportedLocks);
generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
} else if (property.equals("lockdiscovery")) {
if (!generateLockDiscovery(path, generatedXML)) {
propertiesNotFound.addElement(property);
}
} else {
propertiesNotFound.addElement(property);
}
}
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
Enumeration propertiesNotFoundList = propertiesNotFound.elements();
if (propertiesNotFoundList.hasMoreElements()) {
status = new String(
"HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
+ " " + WebdavStatus.getStatusText
(WebdavStatus.SC_NOT_FOUND));
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
while (propertiesNotFoundList.hasMoreElements()) {
generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
}
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
}
break;
}
generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
}
/**
* If a directory does not have files or folders under it then this
* method generates the necessary xml
*
* @param context Description of the Parameter
* @param resources Description of the Parameter
* @param generatedXML Description of the Parameter
* @param propertiesVector Description of the Parameter
* @throws java.io.IOException Description of the Exception
*/
private void parseProperties(ActionContext context, ModuleContext resources,
XMLWriter generatedXML, Vector propertiesVector) throws IOException {
generatedXML.writeElement(null, "response", XMLWriter.OPENING);
String status = new String(
"HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
+ " " + WebdavStatus.getStatusText
(WebdavStatus.SC_NOT_FOUND));
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
}
/**
* Propfind helper method. Dispays the properties of a lock-null resource.
*
* @param generatedXML XML response to the Propfind request
* @param path Path of the current resource
* @param type Propfind type
* @param propertiesVector If the propfind type is find properties by name,
* then this Vector contains those properties
* @param req Description of the Parameter
*/
private void parseLockNullProperties(HttpServletRequest req,
XMLWriter generatedXML,
String path, int type,
Vector propertiesVector) {
// Exclude any resource in the /WEB-INF and /META-INF subdirectories
// (the "toUpperCase()" avoids problems on Windows systems)
if (path.toUpperCase().startsWith("/WEB-INF") ||
path.toUpperCase().startsWith("/META-INF")) {
return;
}
// Retrieving the lock associated with the lock-null resource
WebdavServlet.LockInfo lock = (WebdavServlet.LockInfo) resourceLocks.get(path);
if (lock == null) {
return;
}
generatedXML.writeElement(null, "response", XMLWriter.OPENING);
String status = new String(
"HTTP/1.1 " + WebdavStatus.SC_OK + " "
+ WebdavStatus.getStatusText
(WebdavStatus.SC_OK));
// Generating href element
generatedXML.writeElement(null, "href", XMLWriter.OPENING);
String absoluteUri = req.getRequestURI();
String relativePath = getRelativePath(req);
String toAppend = path.substring(relativePath.length());
if (!toAppend.startsWith("/")) {
toAppend = "/" + toAppend;
}
generatedXML.writeText(rewriteUrl(normalize(absoluteUri + toAppend)));
generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
String resourceName = path;
//System.out.println("Resource Name: " + resourceName);
int lastSlash = path.lastIndexOf('/');
if (lastSlash != -1) {
resourceName = resourceName.substring(lastSlash + 1);
}
switch (type) {
case FIND_ALL_PROP:
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
generatedXML.writeProperty
(
null, "creationdate",
getISOCreationDate(lock.creationDate.getTime()));
generatedXML.writeElement
(null, "displayname", XMLWriter.OPENING);
generatedXML.writeData(resourceName);
generatedXML.writeElement
(null, "displayname", XMLWriter.CLOSING);
generatedXML.writeProperty(
null, "getlastmodified",
FastHttpDateFormat.formatDate
(lock.creationDate.getTime(), null));
generatedXML.writeProperty
(null, "getcontentlength", String.valueOf(0));
generatedXML.writeProperty(null, "getcontenttype", "");
generatedXML.writeProperty(null, "getetag", "");
generatedXML.writeElement(
null, "resourcetype",
XMLWriter.OPENING);
generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "resourcetype",
XMLWriter.CLOSING);
generatedXML.writeProperty(null, "source", "");
String supportedLocks = "<lockentry>"
+ "<lockscope><exclusive/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>" + "<lockentry>"
+ "<lockscope><shared/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>";
generatedXML.writeElement(
null, "supportedlock",
XMLWriter.OPENING);
generatedXML.writeText(supportedLocks);
generatedXML.writeElement(
null, "supportedlock",
XMLWriter.CLOSING);
generateLockDiscovery(path, generatedXML);
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
break;
case FIND_PROPERTY_NAMES:
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
generatedXML.writeElement(
null, "creationdate",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "displayname",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "getcontentlanguage",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "getcontentlength",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "getcontenttype",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "getetag",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "getlastmodified",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "resourcetype",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "source",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "lockdiscovery",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
break;
case FIND_BY_PROPERTY:
Vector propertiesNotFound = new Vector();
// Parse the list of properties
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
Enumeration properties = propertiesVector.elements();
while (properties.hasMoreElements()) {
String property = (String) properties.nextElement();
if (property.equals("creationdate")) {
generatedXML.writeProperty
(
null, "creationdate",
getISOCreationDate(lock.creationDate.getTime()));
} else if (property.equals("displayname")) {
generatedXML.writeElement
(null, "displayname", XMLWriter.OPENING);
generatedXML.writeData(resourceName);
generatedXML.writeElement
(null, "displayname", XMLWriter.CLOSING);
} else if (property.equals("getcontentlanguage")) {
generatedXML.writeElement(
null, "getcontentlanguage",
XMLWriter.NO_CONTENT);
} else if (property.equals("getcontentlength")) {
generatedXML.writeProperty
(null, "getcontentlength", (String.valueOf(0)));
} else if (property.equals("getcontenttype")) {
generatedXML.writeProperty
(null, "getcontenttype", "");
} else if (property.equals("getetag")) {
generatedXML.writeProperty(null, "getetag", "");
} else if (property.equals("getlastmodified")) {
generatedXML.writeProperty
(
null, "getlastmodified",
FastHttpDateFormat.formatDate
(lock.creationDate.getTime(), null));
} else if (property.equals("resourcetype")) {
generatedXML.writeElement(
null, "resourcetype",
XMLWriter.OPENING);
generatedXML.writeElement(
null, "lock-null",
XMLWriter.NO_CONTENT);
generatedXML.writeElement(
null, "resourcetype",
XMLWriter.CLOSING);
} else if (property.equals("source")) {
generatedXML.writeProperty(null, "source", "");
} else if (property.equals("supportedlock")) {
supportedLocks = "<lockentry>"
+ "<lockscope><exclusive/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>" + "<lockentry>"
+ "<lockscope><shared/></lockscope>"
+ "<locktype><write/></locktype>"
+ "</lockentry>";
generatedXML.writeElement(
null, "supportedlock",
XMLWriter.OPENING);
generatedXML.writeText(supportedLocks);
generatedXML.writeElement(
null, "supportedlock",
XMLWriter.CLOSING);
} else if (property.equals("lockdiscovery")) {
if (!generateLockDiscovery(path, generatedXML)) {
propertiesNotFound.addElement(property);
}
} else {
propertiesNotFound.addElement(property);
}
}
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
Enumeration propertiesNotFoundList = propertiesNotFound.elements();
if (propertiesNotFoundList.hasMoreElements()) {
status = new String(
"HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
+ " " + WebdavStatus.getStatusText
(WebdavStatus.SC_NOT_FOUND));
generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
while (propertiesNotFoundList.hasMoreElements()) {
generatedXML.writeElement
(
null, (String) propertiesNotFoundList.nextElement(),
XMLWriter.NO_CONTENT);
}
generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
generatedXML.writeElement(null, "status", XMLWriter.OPENING);
generatedXML.writeText(status);
generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
}
break;
}
generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
}
/**
* Print the lock discovery information associated with a path.
*
* @param path Path
* @param generatedXML XML data to which the locks info will be appended
* @return true if at least one lock was displayed
*/
private boolean generateLockDiscovery
(String path, XMLWriter generatedXML) {
WebdavServlet.LockInfo resourceLock = (WebdavServlet.LockInfo) resourceLocks.get(path);
Enumeration collectionLocksList = collectionLocks.elements();
boolean wroteStart = false;
if (resourceLock != null) {
wroteStart = true;
generatedXML.writeElement(
null, "lockdiscovery",
XMLWriter.OPENING);
resourceLock.toXML(generatedXML);
}
while (collectionLocksList.hasMoreElements()) {
WebdavServlet.LockInfo currentLock =
(WebdavServlet.LockInfo) collectionLocksList.nextElement();
if (path.startsWith(currentLock.path)) {
if (!wroteStart) {
wroteStart = true;
generatedXML.writeElement(
null, "lockdiscovery",
XMLWriter.OPENING);
}
currentLock.toXML(generatedXML);
}
}
if (wroteStart) {
generatedXML.writeElement(
null, "lockdiscovery",
XMLWriter.CLOSING);
} else {
return false;
}
return true;
}
/**
* Get creation date in ISO format.
*
* @param creationDate Description of the Parameter
* @return The iSOCreationDate value
*/
private String getISOCreationDate(long creationDate) {
StringBuffer creationDateValue = new StringBuffer
(
creationDateFormat.format
(new Date(creationDate)));
/*
* 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 < 10)
* creationDateValue.append("0");
* creationDateValue.append(offset + ":00");
* } else {
* creationDateValue.append("Z");
* }
*/
return creationDateValue.toString();
}
/**
* Determines the methods normally allowed for the resource.
*
* @param resources Description of the Parameter
* @param req Description of the Parameter
* @return Description of the Return Value
*/
private StringBuffer determineMethodsAllowed(DirContext resources,
HttpServletRequest req) {
StringBuffer methodsAllowed = new StringBuffer();
boolean exists = true;
Object object = null;
try {
String path = getRelativePath(req);
object = resources.lookup(path);
} catch (NamingException e) {
exists = false;
}
if (!exists) {
methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
return methodsAllowed;
}
methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
//if (listings) {
methodsAllowed.append(", PROPFIND");
//}
if (!(object instanceof DirContext)) {
methodsAllowed.append(", PUT");
}
return methodsAllowed;
}
// -------------------------------------------------- LockInfo Inner Class
/**
* Holds a lock information.
*
* @author matt rajkowski
* @version $Id: WebdavServlet.java,v 1.1.2.2 2004/11/17 23:25:29 ananth Exp
* $
* @created July 12, 2004
*/
private class LockInfo {
// -------------------------------------------------------- Constructor
/**
* Constructor.
*/
public LockInfo() {
}
// ------------------------------------------------- Instance Variables
String path = "/";
String type = "write";
String scope = "exclusive";
int depth = 0;
String owner = "";
Vector tokens = new Vector();
long expiresAt = 0;
Date creationDate = new Date();
// ----------------------------------------------------- Public Methods
/**
* Get a String representation of this lock token.
*
* @return Description of the Return Value
*/
public String toString() {
String result = "Type:" + type + "\n";
result += "Scope:" + scope + "\n";
result += "Depth:" + depth + "\n";
result += "Owner:" + owner + "\n";
result += "Expiration:"
+ FastHttpDateFormat.formatDate(expiresAt, null) + "\n";
Enumeration tokensList = tokens.elements();
while (tokensList.hasMoreElements()) {
result += "Token:" + tokensList.nextElement() + "\n";
}
return result;
}
/**
* Return true if the lock has expired.
*
* @return Description of the Return Value
*/
public boolean hasExpired() {
return (System.currentTimeMillis() > expiresAt);
}
/**
* Return true if the lock is exclusive.
*
* @return The exclusive value
*/
public boolean isExclusive() {
return (scope.equals("exclusive"));
}
/**
* Get an XML representation of this lock token. This method will append an
* XML fragment to the given XML writer.
*
* @param generatedXML Description of the Parameter
*/
public void toXML(XMLWriter generatedXML) {
generatedXML.writeElement(null, "activelock", XMLWriter.OPENING);
generatedXML.writeElement(null, "locktype", XMLWriter.OPENING);
generatedXML.writeElement(null, type, XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "locktype", XMLWriter.CLOSING);
generatedXML.writeElement(null, "lockscope", XMLWriter.OPENING);
generatedXML.writeElement(null, scope, XMLWriter.NO_CONTENT);
generatedXML.writeElement(null, "lockscope", XMLWriter.CLOSING);
generatedXML.writeElement(null, "depth", XMLWriter.OPENING);
if (depth == INFINITY) {
generatedXML.writeText("Infinity");
} else {
generatedXML.writeText("0");
}
generatedXML.writeElement(null, "depth", XMLWriter.CLOSING);
generatedXML.writeElement(null, "owner", XMLWriter.OPENING);
generatedXML.writeText(owner);
generatedXML.writeElement(null, "owner", XMLWriter.CLOSING);
generatedXML.writeElement(null, "timeout", XMLWriter.OPENING);
long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
generatedXML.writeText("Second-" + timeout);
generatedXML.writeElement(null, "timeout", XMLWriter.CLOSING);
generatedXML.writeElement(null, "locktoken", XMLWriter.OPENING);
Enumeration tokensList = tokens.elements();
while (tokensList.hasMoreElements()) {
generatedXML.writeElement(null, "href", XMLWriter.OPENING);
generatedXML.writeText(
"opaquelocktoken:"
+ tokensList.nextElement());
generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
}
generatedXML.writeElement(null, "locktoken", XMLWriter.CLOSING);
generatedXML.writeElement(null, "activelock", XMLWriter.CLOSING);
}
}
// --------------------------------------------------- Property Inner Class
/**
* Description of the Class
*
* @author matt rajkowski
* @version $Id: WebdavServlet.java,v 1.1.2.2 2004/11/17 23:25:29 ananth Exp
* $
* @created July 12, 2004
*/
private class Property {
public String name;
public String value;
public String namespace;
public String namespaceAbbrev;
public int status = WebdavStatus.SC_OK;
}
}
/**
* Wraps the HttpServletResponse class to abstract the specific protocol used.
* To support other protocols we would only need to modify this class and the
* WebDavRetCode classes.
*
* @author Marc Eaddy
* @version 1.0, 16 Nov 1997
* @created July 12, 2004
*/
class WebdavStatus {
// ----------------------------------------------------- Instance Variables
/**
* This Hashtable contains the mapping of HTTP and WebDAV status codes to
* descriptive text. This is a static variable.
*/
private static Hashtable mapStatusCodes = new Hashtable();
// ------------------------------------------------------ HTTP Status Codes
/**
* Status code (200) indicating the request succeeded normally.
*/
public final static int SC_OK = HttpServletResponse.SC_OK;
/**
* Status code (201) indicating the request succeeded and created a new
* resource on the server.
*/
public final static int SC_CREATED = HttpServletResponse.SC_CREATED;
/**
* Status code (202) indicating that a request was accepted for processing,
* but was not completed.
*/
public final static int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
/**
* Status code (204) indicating that the request succeeded but that there was
* no new information to return.
*/
public final static int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
/**
* 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.
*/
public final static int SC_MOVED_PERMANENTLY =
HttpServletResponse.SC_MOVED_PERMANENTLY;
/**
* 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.
*/
public final static int SC_MOVED_TEMPORARILY =
HttpServletResponse.SC_MOVED_TEMPORARILY;
/**
* Status code (304) indicating that a conditional GET operation found that
* the resource was available and not modified.
*/
public final static int SC_NOT_MODIFIED =
HttpServletResponse.SC_NOT_MODIFIED;
/**
* Status code (400) indicating the request sent by the client was
* syntactically incorrect.
*/
public final static int SC_BAD_REQUEST =
HttpServletResponse.SC_BAD_REQUEST;
/**
* Status code (401) indicating that the request requires HTTP
* authentication.
*/
public final static int SC_UNAUTHORIZED =
HttpServletResponse.SC_UNAUTHORIZED;
/**
* Status code (403) indicating the server understood the request but refused
* to fulfill it.
*/
public final static int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
/**
* Status code (404) indicating that the requested resource is not available.
*/
public final static int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
/**
* Status code (500) indicating an error inside the HTTP service which
* prevented it from fulfilling the request.
*/
public final static int SC_INTERNAL_SERVER_ERROR =
HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
/**
* Status code (501) indicating the HTTP service does not support the
* functionality needed to fulfill the request.
*/
public final static int SC_NOT_IMPLEMENTED =
HttpServletResponse.SC_NOT_IMPLEMENTED;
/**
* Status code (502) indicating that the HTTP server received an invalid
* response from a server it consulted when acting as a proxy or gateway.
*/
public final static int SC_BAD_GATEWAY =
HttpServletResponse.SC_BAD_GATEWAY;
/**
* Status code (503) indicating that the HTTP service is temporarily
* overloaded, and unable to handle the request.
*/
public final static int SC_SERVICE_UNAVAILABLE =
HttpServletResponse.SC_SERVICE_UNAVAILABLE;
/**
* 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.
*/
public final static int SC_CONTINUE = 100;
/**
* Status code (405) indicating the method specified is not allowed for the
* resource.
*/
public final static int SC_METHOD_NOT_ALLOWED = 405;
/**
* Status code (409) indicating that the request could not be completed due
* to a conflict with the current state of the resource.
*/
public final static int SC_CONFLICT = 409;
/**
* 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.
*/
public final static int SC_PRECONDITION_FAILED = 412;
/**
* 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.
*/
public final static int SC_REQUEST_TOO_LONG = 413;
/**
* 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.
*/
public final static int SC_UNSUPPORTED_MEDIA_TYPE = 415;
// -------------------------------------------- Extended WebDav status code
/**
* Status code (207) indicating that the response requires providing status
* for multiple independent operations.
*/
public final static int SC_MULTI_STATUS = 207;
// This one colides with HTTP 1.1
// "207 Parital Update OK"
/**
* Status code (418) indicating the entity body submitted with the PATCH
* method was not understood by the resource.
*/
public final static int SC_UNPROCESSABLE_ENTITY = 418;
// This one colides with HTTP 1.1
// "418 Reauthentication Required"
/**
* 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.
*/
public final static int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
// This one colides with HTTP 1.1
// "419 Proxy Reauthentication Required"
/**
* 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.
*/
public final static int SC_METHOD_FAILURE = 420;
/**
* 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.
*/
public final static int SC_LOCKED = 423;
// ------------------------------------------------------------ Initializer
static {
// HTTP 1.0 tatus Code
addStatusCodeMap(SC_OK, "OK");
addStatusCodeMap(SC_CREATED, "Created");
addStatusCodeMap(SC_ACCEPTED, "Accepted");
addStatusCodeMap(SC_NO_CONTENT, "No Content");
addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
addStatusCodeMap(SC_NOT_FOUND, "Not Found");
addStatusCodeMap(SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
addStatusCodeMap(SC_CONTINUE, "Continue");
addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
addStatusCodeMap(SC_CONFLICT, "Conflict");
addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type");
// WebDav Status Codes
addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
addStatusCodeMap(SC_UNPROCESSABLE_ENTITY, "Unprocessable Entity");
addStatusCodeMap(
SC_INSUFFICIENT_SPACE_ON_RESOURCE,
"Insufficient Space On Resource");
addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
addStatusCodeMap(SC_LOCKED, "Locked");
}
// --------------------------------------------------------- Public Methods
/**
* 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.
*
* @param nHttpStatusCode [IN] HTTP or WebDAV status code
* @return A string with a short descriptive phrase for the
* HTTP status code (e.g., "OK").
*/
public static String getStatusText(int nHttpStatusCode) {
Integer intKey = new Integer(nHttpStatusCode);
if (!mapStatusCodes.containsKey(intKey)) {
return "";
} else {
return (String) mapStatusCodes.get(intKey);
}
}
// -------------------------------------------------------- Private Methods
/**
* Adds a new status code -> status text mapping. This is a static method
* because the mapping is a static variable.
*
* @param nKey [IN] HTTP or WebDAV status code
* @param strVal [IN] HTTP status text
*/
private static void addStatusCodeMap(int nKey, String strVal) {
mapStatusCodes.put(new Integer(nKey), strVal);
}
}