2 * Copyright 2007, 2008, 2009 Electronic Business Systems Ltd.
4 * This file is part of GSS.
6 * GSS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GSS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GSS. If not, see <http://www.gnu.org/licenses/>.
19 package gr.ebs.gss.server.webdav;
21 import gr.ebs.gss.client.exceptions.DuplicateNameException;
22 import gr.ebs.gss.client.exceptions.GSSIOException;
23 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
24 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
25 import gr.ebs.gss.client.exceptions.QuotaExceededException;
26 import gr.ebs.gss.client.exceptions.RpcException;
27 import gr.ebs.gss.server.domain.User;
28 import gr.ebs.gss.server.domain.dto.AuditInfoDTO;
29 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
30 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
31 import gr.ebs.gss.server.domain.dto.FolderDTO;
32 import gr.ebs.gss.server.ejb.ExternalAPI;
34 import java.io.BufferedInputStream;
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
38 import java.io.FileInputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.InputStreamReader;
42 import java.io.OutputStreamWriter;
43 import java.io.PrintWriter;
44 import java.io.RandomAccessFile;
45 import java.io.Reader;
46 import java.io.StringReader;
47 import java.io.StringWriter;
48 import java.io.Writer;
49 import java.security.MessageDigest;
50 import java.security.NoSuchAlgorithmException;
51 import java.text.SimpleDateFormat;
52 import java.util.ArrayList;
53 import java.util.Date;
54 import java.util.Enumeration;
55 import java.util.Hashtable;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.Stack;
60 import java.util.StringTokenizer;
61 import java.util.TimeZone;
62 import java.util.Vector;
64 import javax.naming.Context;
65 import javax.naming.InitialContext;
66 import javax.naming.NamingException;
67 import javax.rmi.PortableRemoteObject;
68 import javax.servlet.ServletContext;
69 import javax.servlet.ServletException;
70 import javax.servlet.ServletOutputStream;
71 import javax.servlet.UnavailableException;
72 import javax.servlet.http.HttpServlet;
73 import javax.servlet.http.HttpServletRequest;
74 import javax.servlet.http.HttpServletResponse;
75 import javax.xml.parsers.DocumentBuilder;
76 import javax.xml.parsers.DocumentBuilderFactory;
77 import javax.xml.parsers.ParserConfigurationException;
79 import org.apache.commons.httpclient.HttpStatus;
80 import org.apache.commons.logging.Log;
81 import org.apache.commons.logging.LogFactory;
82 import org.w3c.dom.Document;
83 import org.w3c.dom.Element;
84 import org.w3c.dom.Node;
85 import org.w3c.dom.NodeList;
86 import org.xml.sax.EntityResolver;
87 import org.xml.sax.InputSource;
88 import org.xml.sax.SAXException;
91 * The implementation of the WebDAV service.
95 public class Webdav extends HttpServlet {
98 * The request attribute containing the user who owns the requested
101 protected static final String OWNER_ATTRIBUTE = "owner";
104 * The request attribute containing the user making the request.
106 protected static final String USER_ATTRIBUTE = "user";
111 private static Log logger = LogFactory.getLog(Webdav.class);
116 protected static final String METHOD_GET = "GET";
121 protected static final String METHOD_POST = "POST";
126 protected static final String METHOD_PUT = "PUT";
131 protected static final String METHOD_DELETE = "DELETE";
136 protected static final String METHOD_HEAD = "HEAD";
141 private static final String METHOD_OPTIONS = "OPTIONS";
146 private static final String METHOD_PROPFIND = "PROPFIND";
151 private static final String METHOD_PROPPATCH = "PROPPATCH";
156 private static final String METHOD_MKCOL = "MKCOL";
161 private static final String METHOD_COPY = "COPY";
166 private static final String METHOD_MOVE = "MOVE";
171 private static final String METHOD_LOCK = "LOCK";
176 private static final String METHOD_UNLOCK = "UNLOCK";
179 * Default depth is infinite.
181 static final int INFINITY = 3; // To limit tree browsing a bit
184 * PROPFIND - Specify a property mask.
186 private static final int FIND_BY_PROPERTY = 0;
189 * PROPFIND - Display all properties.
191 private static final int FIND_ALL_PROP = 1;
194 * PROPFIND - Return property names.
196 private static final int FIND_PROPERTY_NAMES = 2;
201 private static final String DEFAULT_NAMESPACE = "DAV:";
206 private static final int LOCK_CREATION = 0;
211 private static final int LOCK_REFRESH = 1;
214 * Default lock timeout value.
216 private static final int DEFAULT_TIMEOUT = 3600;
219 * Maximum lock timeout.
221 private static final int MAX_TIMEOUT = 604800;
224 * Size of file transfer buffer in bytes.
226 private static final int BUFFER_SIZE = 4096;
229 * The output buffer size to use when serving resources.
231 protected int output = 2048;
234 * The input buffer size to use when serving resources.
236 private int input = 2048;
239 * MIME multipart separation string
241 protected static final String mimeSeparation = "GSS_MIME_BOUNDARY";
244 * Simple date format for the creation date ISO representation (partial).
246 private static final SimpleDateFormat creationDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
251 private static final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
254 * Array containing the safe characters set.
256 private static URLEncoder urlEncoder;
259 * File encoding to be used when reading static files. If none is specified
260 * the platform default is used.
262 private String fileEncoding = null;
265 * The style sheet for displaying the directory listings.
267 private static final String GSS_CSS = "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " + "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " + "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " + "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " + "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " + "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" + "A {color : black;}" + "A.name {color : black;}" + "HR {color : #525D76;}";
270 * Secret information used to generate reasonably secure lock ids.
272 private String secret = "gss-webdav";
275 * Repository of the locks put on single resources.
280 private Hashtable<String, LockInfo> resourceLocks = new Hashtable<String, LockInfo>();
283 * Repository of the lock-null resources.
285 * Key : path of the collection containing the lock-null resource<br>
286 * Value : Vector of lock-null resource which are members of the collection.
287 * Each element of the Vector is the path associated with the lock-null
290 private Hashtable<String, Vector<String>> lockNullResources = new Hashtable<String, Vector<String>>();
293 * Vector of the heritable locks.
298 private Vector<LockInfo> collectionLocks = new Vector<LockInfo>();
303 protected static ArrayList FULL = new ArrayList();
306 * MD5 message digest provider.
308 protected static MessageDigest md5Helper;
311 * The MD5 helper object for this class.
313 protected static final MD5Encoder md5Encoder = new MD5Encoder();
316 * GMT timezone - all HTTP dates are on GMT
319 creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
320 urlEncoder = new URLEncoder();
321 urlEncoder.addSafeCharacter('-');
322 urlEncoder.addSafeCharacter('_');
323 urlEncoder.addSafeCharacter('.');
324 urlEncoder.addSafeCharacter('*');
325 urlEncoder.addSafeCharacter('/');
329 public void init() throws ServletException {
330 if (getServletConfig().getInitParameter("input") != null)
331 input = Integer.parseInt(getServletConfig().getInitParameter("input"));
333 if (getServletConfig().getInitParameter("output") != null)
334 output = Integer.parseInt(getServletConfig().getInitParameter("output"));
336 fileEncoding = getServletConfig().getInitParameter("fileEncoding");
338 // Sanity check on the specified buffer sizes
343 if (logger.isDebugEnabled())
344 logger.debug("Input buffer size=" + input + ", output buffer size=" + output);
346 if (getServletConfig().getInitParameter("secret") != null)
347 secret = getServletConfig().getInitParameter("secret");
349 // Load the MD5 helper used to calculate signatures.
351 md5Helper = MessageDigest.getInstance("MD5");
352 } catch (NoSuchAlgorithmException e) {
353 throw new UnavailableException("No MD5");
358 * A helper method that retrieves a reference to the ExternalAPI bean and
359 * stores it for future use.
361 * @return an ExternalAPI instance
362 * @throws RpcException in case an error occurs
364 protected ExternalAPI getService() throws RpcException {
366 final Context ctx = new InitialContext();
367 final Object ref = ctx.lookup("gss/ExternalAPIBean/local");
368 return (ExternalAPI) PortableRemoteObject.narrow(ref, ExternalAPI.class);
369 } catch (final NamingException e) {
370 logger.error("Unable to retrieve the ExternalAPI EJB", e);
371 throw new RpcException("An error occurred while contacting the naming service");
376 public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
377 String method = request.getMethod();
379 if (logger.isDebugEnabled()) {
380 String path = request.getPathInfo();
382 path = request.getServletPath();
383 if (path == null || path.equals(""))
385 logger.debug("[" + method + "] " + path);
390 if (request.getUserPrincipal() != null) { // Let unauthenticated
391 // OPTIONS go through;
392 // all others will be
394 // authentication anyway
395 // before we get here.
396 user = getService().findUser(request.getUserPrincipal().getName());
398 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
402 request.setAttribute(USER_ATTRIBUTE, user);
403 request.setAttribute(OWNER_ATTRIBUTE, user);
404 } catch (RpcException e) {
405 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
408 if (method.equals(METHOD_GET))
409 doGet(request, response);
410 else if (method.equals(METHOD_POST))
411 doPost(request, response);
412 else if (method.equals(METHOD_PUT))
413 doPut(request, response);
414 else if (method.equals(METHOD_DELETE))
415 doDelete(request, response);
416 else if (method.equals(METHOD_HEAD))
417 doHead(request, response);
418 else if (method.equals(METHOD_PROPFIND))
419 doPropfind(request, response);
420 else if (method.equals(METHOD_PROPPATCH))
421 doProppatch(request, response);
422 else if (method.equals(METHOD_MKCOL))
423 doMkcol(request, response);
424 else if (method.equals(METHOD_COPY))
425 doCopy(request, response);
426 else if (method.equals(METHOD_MOVE))
427 doMove(request, response);
428 else if (method.equals(METHOD_LOCK))
429 doLock(request, response);
430 else if (method.equals(METHOD_UNLOCK))
431 doUnlock(request, response);
432 else if (method.equals(METHOD_OPTIONS))
433 doOptions(request, response);
435 // DefaultServlet processing for TRACE, etc.
436 super.service(request, response);
440 protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException {
441 resp.addHeader("DAV", "1,2");
442 StringBuffer methodsAllowed = new StringBuffer();
444 methodsAllowed = determineMethodsAllowed(req);
445 } catch (RpcException e) {
446 resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
449 resp.addHeader("Allow", methodsAllowed.toString());
450 resp.addHeader("MS-Author-Via", "DAV");
454 * Implement the PROPFIND method.
456 * @param req the HTTP request
457 * @param resp the HTTP response
458 * @throws ServletException
459 * @throws IOException
461 private void doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
462 String path = getRelativePath(req);
463 if (path.endsWith("/") && !path.equals("/"))
464 path = path.substring(0, path.length() - 1);
466 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
467 resp.sendError(WebdavStatus.SC_FORBIDDEN);
471 // Properties which are to be displayed.
472 Vector<String> properties = null;
474 int depth = INFINITY;
476 int type = FIND_ALL_PROP;
478 String depthStr = req.getHeader("Depth");
480 if (depthStr == null)
482 else if (depthStr.equals("0"))
484 else if (depthStr.equals("1"))
486 else if (depthStr.equals("infinity"))
489 Node propNode = null;
491 if (req.getInputStream().available() > 0) {
492 DocumentBuilder documentBuilder = getDocumentBuilder();
495 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
497 // Get the root element of the document
498 Element rootElement = document.getDocumentElement();
499 NodeList childList = rootElement.getChildNodes();
501 for (int i = 0; i < childList.getLength(); i++) {
502 Node currentNode = childList.item(i);
503 switch (currentNode.getNodeType()) {
506 case Node.ELEMENT_NODE:
507 if (currentNode.getNodeName().endsWith("prop")) {
508 type = FIND_BY_PROPERTY;
509 propNode = currentNode;
511 if (currentNode.getNodeName().endsWith("propname"))
512 type = FIND_PROPERTY_NAMES;
513 if (currentNode.getNodeName().endsWith("allprop"))
514 type = FIND_ALL_PROP;
518 } catch (SAXException e) {
519 // Something went wrong - use the defaults.
520 if (logger.isDebugEnabled())
521 logger.debug(e.getMessage());
522 } catch (IOException e) {
523 // Something went wrong - use the defaults.
524 if (logger.isDebugEnabled())
525 logger.debug(e.getMessage());
529 if (type == FIND_BY_PROPERTY) {
530 properties = new Vector<String>();
531 NodeList childList = propNode.getChildNodes();
533 for (int i = 0; i < childList.getLength(); i++) {
534 Node currentNode = childList.item(i);
535 switch (currentNode.getNodeType()) {
538 case Node.ELEMENT_NODE:
539 String nodeName = currentNode.getNodeName();
540 String propertyName = null;
541 if (nodeName.indexOf(':') != -1)
542 propertyName = nodeName.substring(nodeName.indexOf(':') + 1);
544 propertyName = nodeName;
545 // href is a live property which is handled differently
546 properties.addElement(propertyName);
551 User user = getUser(req);
552 boolean exists = true;
553 Object object = null;
555 object = getService().getResourceAtPath(user.getId(), path, true);
556 } catch (ObjectNotFoundException e) {
558 int slash = path.lastIndexOf('/');
560 String parentPath = path.substring(0, slash);
561 Vector currentLockNullResources = lockNullResources.get(parentPath);
562 if (currentLockNullResources != null) {
563 Enumeration lockNullResourcesList = currentLockNullResources.elements();
564 while (lockNullResourcesList.hasMoreElements()) {
565 String lockNullPath = (String) lockNullResourcesList.nextElement();
566 if (lockNullPath.equals(path)) {
567 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
568 resp.setContentType("text/xml; charset=UTF-8");
569 // Create multistatus object
570 XMLWriter generatedXML = new XMLWriter(resp.getWriter());
571 generatedXML.writeXMLHeader();
572 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
573 parseLockNullProperties(req, generatedXML, lockNullPath, type, properties);
574 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
575 generatedXML.sendData();
581 } catch (RpcException e) {
582 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
586 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
589 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
590 resp.setContentType("text/xml; charset=UTF-8");
591 // Create multistatus object
592 XMLWriter generatedXML = new XMLWriter(resp.getWriter());
593 generatedXML.writeXMLHeader();
594 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
596 parseProperties(req, generatedXML, path, type, properties, object);
598 // The stack always contains the object of the current level
599 Stack<String> stack = new Stack<String>();
602 // Stack of the objects one level below
603 Stack<String> stackBelow = new Stack<String>();
604 while (!stack.isEmpty() && depth >= 0) {
605 String currentPath = stack.pop();
607 object = getService().getResourceAtPath(user.getId(), currentPath, true);
608 } catch (ObjectNotFoundException e) {
610 } catch (RpcException e) {
611 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
614 parseProperties(req, generatedXML, currentPath, type, properties, object);
615 if (object instanceof FolderDTO && depth > 0) {
616 FolderDTO folder = (FolderDTO) object;
617 // Retrieve the subfolders.
618 List subfolders = folder.getSubfolders();
619 Iterator iter = subfolders.iterator();
620 while (iter.hasNext()) {
621 FolderDTO f = (FolderDTO) iter.next();
622 String newPath = currentPath;
623 if (!newPath.endsWith("/"))
625 newPath += f.getName();
626 stackBelow.push(newPath);
628 // Retrieve the files.
629 List<FileHeaderDTO> files;
631 files = getService().getFiles(user.getId(), folder.getId(), true);
632 } catch (ObjectNotFoundException e) {
633 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
635 } catch (InsufficientPermissionsException e) {
636 resp.sendError(HttpServletResponse.SC_FORBIDDEN, path);
638 } catch (RpcException e) {
639 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
642 for (FileHeaderDTO file : files) {
643 String newPath = currentPath;
644 if (!newPath.endsWith("/"))
646 newPath += file.getName();
647 stackBelow.push(newPath);
649 // Displaying the lock-null resources present in that
651 String lockPath = currentPath;
652 if (lockPath.endsWith("/"))
653 lockPath = lockPath.substring(0, lockPath.length() - 1);
654 Vector currentLockNullResources = lockNullResources.get(lockPath);
655 if (currentLockNullResources != null) {
656 Enumeration lockNullResourcesList = currentLockNullResources.elements();
657 while (lockNullResourcesList.hasMoreElements()) {
658 String lockNullPath = (String) lockNullResourcesList.nextElement();
659 parseLockNullProperties(req, generatedXML, lockNullPath, type, properties);
663 if (stack.isEmpty()) {
666 stackBelow = new Stack<String>();
668 generatedXML.sendData();
671 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
672 generatedXML.sendData();
678 * @param req the HTTP request
679 * @param resp the HTTP response
680 * @throws IOException if an error occurs while sending the response
682 private void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
684 resp.sendError(WebdavStatus.SC_LOCKED);
687 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
691 * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
694 protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
696 resp.sendError(WebdavStatus.SC_LOCKED);
699 deleteResource(req, resp);
703 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
704 // Serve the requested resource, including the data content
706 serveResource(req, resp, true);
707 } catch (ObjectNotFoundException e) {
708 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
710 } catch (InsufficientPermissionsException e) {
711 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
713 } catch (RpcException e) {
714 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
720 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
721 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
725 protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
727 resp.sendError(WebdavStatus.SC_LOCKED);
731 User user = getUser(req);
732 String path = getRelativePath(req);
733 boolean exists = true;
734 Object resource = null;
735 FileHeaderDTO file = null;
737 resource = getService().getResourceAtPath(user.getId(), path, true);
738 } catch (ObjectNotFoundException e) {
740 } catch (RpcException e) {
741 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
746 if (resource instanceof FileHeaderDTO)
747 file = (FileHeaderDTO) resource;
749 resp.sendError(HttpServletResponse.SC_CONFLICT);
752 boolean result = true;
754 // Temporary content file used to support partial PUT.
755 File contentFile = null;
757 Range range = parseContentRange(req, resp);
759 InputStream resourceInputStream = null;
761 // Append data specified in ranges to existing content for this
762 // resource - create a temporary file on the local filesystem to
763 // perform this operation.
764 // Assume just one range is specified for now
767 contentFile = executePartialPut(req, range, path);
768 } catch (RpcException e) {
769 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
771 } catch (ObjectNotFoundException e) {
772 resp.sendError(HttpServletResponse.SC_CONFLICT);
774 } catch (InsufficientPermissionsException e) {
775 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
778 resourceInputStream = new FileInputStream(contentFile);
780 resourceInputStream = req.getInputStream();
783 FolderDTO folder = null;
784 Object parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
785 if (!(parent instanceof FolderDTO)) {
786 resp.sendError(HttpServletResponse.SC_CONFLICT);
789 folder = (FolderDTO) parent;
790 String name = getLastElement(path);
791 String mimeType = getServletContext().getMimeType(name);
792 // FIXME: Add attributes
794 getService().updateFileContents(user.getId(), file.getId(), mimeType, resourceInputStream);
796 getService().createFile(user.getId(), folder.getId(), name, mimeType, resourceInputStream);
797 } catch (ObjectNotFoundException e) {
799 } catch (InsufficientPermissionsException e) {
800 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
802 } catch (QuotaExceededException e) {
803 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
805 } catch (GSSIOException e) {
806 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
808 } catch (RpcException e) {
809 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
811 } catch (DuplicateNameException e) {
812 resp.sendError(HttpServletResponse.SC_CONFLICT);
818 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
820 resp.setStatus(HttpServletResponse.SC_CREATED);
822 resp.sendError(HttpServletResponse.SC_CONFLICT);
824 // Removing any lock-null resource which would be present.
825 lockNullResources.remove(path);
829 protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
830 // Serve the requested resource, without the data content
832 serveResource(req, resp, false);
833 } catch (ObjectNotFoundException e) {
834 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
836 } catch (InsufficientPermissionsException e) {
837 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
839 } catch (RpcException e) {
840 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
848 * @param req the HTTP request
849 * @param resp the HTTP response
850 * @throws IOException if an error occurs while sending the response
852 private void doUnlock(HttpServletRequest req, HttpServletResponse resp) throws IOException {
854 resp.sendError(WebdavStatus.SC_LOCKED);
857 String path = getRelativePath(req);
858 String lockTokenHeader = req.getHeader("Lock-Token");
859 if (lockTokenHeader == null)
860 lockTokenHeader = "";
862 // Checking resource locks
863 LockInfo lock = resourceLocks.get(path);
864 Enumeration tokenList = null;
866 // At least one of the tokens of the locks must have been given
867 tokenList = lock.tokens.elements();
868 while (tokenList.hasMoreElements()) {
869 String token = (String) tokenList.nextElement();
870 if (lockTokenHeader.indexOf(token) != -1)
871 lock.tokens.removeElement(token);
873 if (lock.tokens.isEmpty()) {
874 resourceLocks.remove(path);
875 // Removing any lock-null resource which would be present
876 lockNullResources.remove(path);
879 // Checking inheritable collection locks
880 Enumeration collectionLocksList = collectionLocks.elements();
881 while (collectionLocksList.hasMoreElements()) {
882 lock = (LockInfo) collectionLocksList.nextElement();
883 if (path.equals(lock.path)) {
884 tokenList = lock.tokens.elements();
885 while (tokenList.hasMoreElements()) {
886 String token = (String) tokenList.nextElement();
887 if (lockTokenHeader.indexOf(token) != -1) {
888 lock.tokens.removeElement(token);
892 if (lock.tokens.isEmpty()) {
893 collectionLocks.removeElement(lock);
894 // Removing any lock-null resource which would be present
895 lockNullResources.remove(path);
899 resp.setStatus(WebdavStatus.SC_NO_CONTENT);
905 * @param req the HTTP request
906 * @param resp the HTTP response
907 * @throws IOException if an error occurs while sending the response
908 * @throws ServletException
910 private void doLock(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
912 resp.sendError(WebdavStatus.SC_LOCKED);
916 LockInfo lock = new LockInfo();
917 // Parsing lock request
919 // Parsing depth header
920 String depthStr = req.getHeader("Depth");
921 if (depthStr == null)
922 lock.depth = INFINITY;
923 else if (depthStr.equals("0"))
926 lock.depth = INFINITY;
928 // Parsing timeout header
929 int lockDuration = DEFAULT_TIMEOUT;
930 String lockDurationStr = req.getHeader("Timeout");
931 if (lockDurationStr == null)
932 lockDuration = DEFAULT_TIMEOUT;
934 int commaPos = lockDurationStr.indexOf(",");
935 // If multiple timeouts, just use the first
937 lockDurationStr = lockDurationStr.substring(0, commaPos);
938 if (lockDurationStr.startsWith("Second-"))
939 lockDuration = new Integer(lockDurationStr.substring(7)).intValue();
940 else if (lockDurationStr.equalsIgnoreCase("infinity"))
941 lockDuration = MAX_TIMEOUT;
944 lockDuration = new Integer(lockDurationStr).intValue();
945 } catch (NumberFormatException e) {
946 lockDuration = MAX_TIMEOUT;
948 if (lockDuration == 0)
949 lockDuration = DEFAULT_TIMEOUT;
950 if (lockDuration > MAX_TIMEOUT)
951 lockDuration = MAX_TIMEOUT;
953 lock.expiresAt = System.currentTimeMillis() + lockDuration * 1000;
955 int lockRequestType = LOCK_CREATION;
956 Node lockInfoNode = null;
957 DocumentBuilder documentBuilder = getDocumentBuilder();
960 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
961 // Get the root element of the document
962 Element rootElement = document.getDocumentElement();
963 lockInfoNode = rootElement;
964 } catch (IOException e) {
965 lockRequestType = LOCK_REFRESH;
966 } catch (SAXException e) {
967 lockRequestType = LOCK_REFRESH;
970 if (lockInfoNode != null) {
971 // Reading lock information
972 NodeList childList = lockInfoNode.getChildNodes();
973 StringWriter strWriter = null;
974 DOMWriter domWriter = null;
976 Node lockScopeNode = null;
977 Node lockTypeNode = null;
978 Node lockOwnerNode = null;
980 for (int i = 0; i < childList.getLength(); i++) {
981 Node currentNode = childList.item(i);
982 switch (currentNode.getNodeType()) {
985 case Node.ELEMENT_NODE:
986 String nodeName = currentNode.getNodeName();
987 if (nodeName.endsWith("lockscope"))
988 lockScopeNode = currentNode;
989 if (nodeName.endsWith("locktype"))
990 lockTypeNode = currentNode;
991 if (nodeName.endsWith("owner"))
992 lockOwnerNode = currentNode;
997 if (lockScopeNode != null) {
998 childList = lockScopeNode.getChildNodes();
999 for (int i = 0; i < childList.getLength(); i++) {
1000 Node currentNode = childList.item(i);
1001 switch (currentNode.getNodeType()) {
1002 case Node.TEXT_NODE:
1004 case Node.ELEMENT_NODE:
1005 String tempScope = currentNode.getNodeName();
1006 if (tempScope.indexOf(':') != -1)
1007 lock.scope = tempScope.substring(tempScope.indexOf(':') + 1);
1009 lock.scope = tempScope;
1013 if (lock.scope == null)
1015 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1018 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1020 if (lockTypeNode != null) {
1021 childList = lockTypeNode.getChildNodes();
1022 for (int i = 0; i < childList.getLength(); i++) {
1023 Node currentNode = childList.item(i);
1024 switch (currentNode.getNodeType()) {
1025 case Node.TEXT_NODE:
1027 case Node.ELEMENT_NODE:
1028 String tempType = currentNode.getNodeName();
1029 if (tempType.indexOf(':') != -1)
1030 lock.type = tempType.substring(tempType.indexOf(':') + 1);
1032 lock.type = tempType;
1037 if (lock.type == null)
1039 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1042 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1044 if (lockOwnerNode != null) {
1045 childList = lockOwnerNode.getChildNodes();
1046 for (int i = 0; i < childList.getLength(); i++) {
1047 Node currentNode = childList.item(i);
1048 switch (currentNode.getNodeType()) {
1049 case Node.TEXT_NODE:
1050 lock.owner += currentNode.getNodeValue();
1052 case Node.ELEMENT_NODE:
1053 strWriter = new StringWriter();
1054 domWriter = new DOMWriter(strWriter, true);
1055 domWriter.setQualifiedNames(false);
1056 domWriter.print(currentNode);
1057 lock.owner += strWriter.toString();
1062 if (lock.owner == null)
1064 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1066 lock.owner = new String();
1069 String path = getRelativePath(req);
1071 User user = getUser(req);
1072 boolean exists = true;
1073 Object object = null;
1075 object = getService().getResourceAtPath(user.getId(), path, true);
1076 } catch (ObjectNotFoundException e) {
1078 } catch (RpcException e) {
1079 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1083 Enumeration locksList = null;
1084 if (lockRequestType == LOCK_CREATION) {
1085 // Generating lock id
1086 String lockTokenStr = req.getServletPath() + "-" + lock.type + "-" + lock.scope + "-" + req.getUserPrincipal() + "-" + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-" + lock.expiresAt + "-" + System.currentTimeMillis() + "-" + secret;
1087 String lockToken = md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
1089 if (exists && object instanceof FolderDTO && lock.depth == INFINITY) {
1091 // Locking a collection (and all its member resources)
1093 // Checking if a child resource of this collection is
1095 Vector<String> lockPaths = new Vector<String>();
1096 locksList = collectionLocks.elements();
1097 while (locksList.hasMoreElements()) {
1098 LockInfo currentLock = (LockInfo) locksList.nextElement();
1099 if (currentLock.hasExpired()) {
1100 resourceLocks.remove(currentLock.path);
1103 if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive()))
1104 // A child collection of this collection is locked
1105 lockPaths.addElement(currentLock.path);
1107 locksList = resourceLocks.elements();
1108 while (locksList.hasMoreElements()) {
1109 LockInfo currentLock = (LockInfo) locksList.nextElement();
1110 if (currentLock.hasExpired()) {
1111 resourceLocks.remove(currentLock.path);
1114 if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive()))
1115 // A child resource of this collection is locked
1116 lockPaths.addElement(currentLock.path);
1119 if (!lockPaths.isEmpty()) {
1120 // One of the child paths was locked
1121 // We generate a multistatus error report
1122 Enumeration lockPathsList = lockPaths.elements();
1123 resp.setStatus(WebdavStatus.SC_CONFLICT);
1124 XMLWriter generatedXML = new XMLWriter();
1125 generatedXML.writeXMLHeader();
1127 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
1129 while (lockPathsList.hasMoreElements()) {
1130 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1131 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1132 generatedXML.writeText((String) lockPathsList.nextElement());
1133 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1134 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1135 generatedXML.writeText("HTTP/1.1 " + WebdavStatus.SC_LOCKED + " " + WebdavStatus.getStatusText(WebdavStatus.SC_LOCKED));
1136 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1137 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1140 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
1142 Writer writer = resp.getWriter();
1143 writer.write(generatedXML.toString());
1148 boolean addLock = true;
1149 // Checking if there is already a shared lock on this path
1150 locksList = collectionLocks.elements();
1151 while (locksList.hasMoreElements()) {
1152 LockInfo currentLock = (LockInfo) locksList.nextElement();
1153 if (currentLock.path.equals(lock.path)) {
1154 if (currentLock.isExclusive()) {
1155 resp.sendError(WebdavStatus.SC_LOCKED);
1157 } else if (lock.isExclusive()) {
1158 resp.sendError(WebdavStatus.SC_LOCKED);
1161 currentLock.tokens.addElement(lockToken);
1167 lock.tokens.addElement(lockToken);
1168 collectionLocks.addElement(lock);
1171 // Locking a single resource
1173 // Retrieving an already existing lock on that resource
1174 LockInfo presentLock = resourceLocks.get(lock.path);
1175 if (presentLock != null) {
1176 if (presentLock.isExclusive() || lock.isExclusive()) {
1177 // If either lock is exclusive, the lock can't be
1179 resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
1182 presentLock.tokens.addElement(lockToken);
1187 lock.tokens.addElement(lockToken);
1188 resourceLocks.put(lock.path, lock);
1189 // Checking if a resource exists at this path
1192 object = getService().getResourceAtPath(user.getId(), path, true);
1193 } catch (ObjectNotFoundException e) {
1195 } catch (RpcException e) {
1196 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1200 // "Creating" a lock-null resource
1201 int slash = lock.path.lastIndexOf('/');
1202 String parentPath = lock.path.substring(0, slash);
1203 Vector<String> lockNulls = lockNullResources.get(parentPath);
1204 if (lockNulls == null) {
1205 lockNulls = new Vector<String>();
1206 lockNullResources.put(parentPath, lockNulls);
1208 lockNulls.addElement(lock.path);
1210 // Add the Lock-Token header as by RFC 2518 8.10.1
1211 // - only do this for newly created locks
1212 resp.addHeader("Lock-Token", "<opaquelocktoken:" + lockToken + ">");
1217 if (lockRequestType == LOCK_REFRESH) {
1218 String ifHeader = req.getHeader("If");
1219 if (ifHeader == null)
1221 // Checking resource locks
1222 LockInfo toRenew = resourceLocks.get(path);
1223 Enumeration tokenList = null;
1225 // At least one of the tokens of the locks must have been given
1226 tokenList = toRenew.tokens.elements();
1227 while (tokenList.hasMoreElements()) {
1228 String token = (String) tokenList.nextElement();
1229 if (ifHeader.indexOf(token) != -1) {
1230 toRenew.expiresAt = lock.expiresAt;
1235 // Checking inheritable collection locks
1236 Enumeration collectionLocksList = collectionLocks.elements();
1237 while (collectionLocksList.hasMoreElements()) {
1238 toRenew = (LockInfo) collectionLocksList.nextElement();
1239 if (path.equals(toRenew.path)) {
1240 tokenList = toRenew.tokens.elements();
1241 while (tokenList.hasMoreElements()) {
1242 String token = (String) tokenList.nextElement();
1243 if (ifHeader.indexOf(token) != -1) {
1244 toRenew.expiresAt = lock.expiresAt;
1251 // Set the status, then generate the XML response containing
1252 // the lock information.
1253 XMLWriter generatedXML = new XMLWriter();
1254 generatedXML.writeXMLHeader();
1255 generatedXML.writeElement(null, "prop" + generateNamespaceDeclarations(), XMLWriter.OPENING);
1256 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
1257 lock.toXML(generatedXML);
1258 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING);
1259 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1261 resp.setStatus(WebdavStatus.SC_OK);
1262 resp.setContentType("text/xml; charset=UTF-8");
1263 Writer writer = resp.getWriter();
1264 writer.write(generatedXML.toString());
1271 * @param req the HTTP request
1272 * @param resp the HTTP response
1273 * @throws IOException if an error occurs while sending the response
1274 * @throws ServletException
1276 private void doMove(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1277 if (isLocked(req)) {
1278 resp.sendError(WebdavStatus.SC_LOCKED);
1282 String path = getRelativePath(req);
1284 if (copyResource(req, resp))
1285 deleteResource(path, req, resp, false);
1291 * @param req the HTTP request
1292 * @param resp the HTTP response
1293 * @throws IOException if an error occurs while sending the response
1294 * @throws ServletException
1296 private void doCopy(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1297 copyResource(req, resp);
1303 * @param req the HTTP request
1304 * @param resp the HTTP response
1305 * @throws IOException if an error occurs while sending the response
1306 * @throws ServletException
1308 private void doMkcol(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1309 if (isLocked(req)) {
1310 resp.sendError(WebdavStatus.SC_LOCKED);
1313 String path = getRelativePath(req);
1314 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
1315 resp.sendError(WebdavStatus.SC_FORBIDDEN);
1319 User user = getUser(req);
1320 boolean exists = true;
1322 getService().getResourceAtPath(user.getId(), path, true);
1323 } catch (ObjectNotFoundException e) {
1325 } catch (RpcException e) {
1326 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1330 // Can't create a collection if a resource already exists at the given
1333 // Get allowed methods.
1334 StringBuffer methodsAllowed;
1336 methodsAllowed = determineMethodsAllowed(req);
1337 } catch (RpcException e) {
1338 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1341 resp.addHeader("Allow", methodsAllowed.toString());
1342 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
1346 if (req.getInputStream().available() > 0) {
1347 DocumentBuilder documentBuilder = getDocumentBuilder();
1349 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
1350 // TODO : Process this request body
1351 resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
1353 } catch (SAXException saxe) {
1354 // Parse error - assume invalid content
1355 resp.sendError(WebdavStatus.SC_BAD_REQUEST);
1362 parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
1363 } catch (ObjectNotFoundException e1) {
1364 resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1366 } catch (RpcException e1) {
1367 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1371 if (parent instanceof FolderDTO) {
1372 FolderDTO folder = (FolderDTO) parent;
1373 getService().createFolder(user.getId(), folder.getId(), getLastElement(path));
1375 resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1378 } catch (DuplicateNameException e) {
1379 // XXX If the existing name is a folder we should be returning
1380 // SC_METHOD_NOT_ALLOWED, or even better, just do the createFolder
1381 // without checking first and then deal with the exceptions.
1382 resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1384 } catch (InsufficientPermissionsException e) {
1385 resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1387 } catch (ObjectNotFoundException e) {
1388 resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1390 } catch (RpcException e) {
1391 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1394 resp.setStatus(WebdavStatus.SC_CREATED);
1395 // Removing any lock-null resource which would be present
1396 lockNullResources.remove(path);
1400 * For a provided path, remove the last element and return the rest, that is
1401 * the path of the parent folder.
1403 * @param path the specified path
1404 * @return the path of the parent folder
1405 * @throws ObjectNotFoundException if the provided string contains no path
1408 protected String getParentPath(String path) throws ObjectNotFoundException {
1409 int lastDelimiter = path.lastIndexOf('/');
1410 if (lastDelimiter == 0)
1412 if (lastDelimiter == -1)
1414 throw new ObjectNotFoundException("There is no parent in the path: " + path);
1415 else if (lastDelimiter < path.length() - 1)
1416 // Return the part before the delimiter.
1417 return path.substring(0, lastDelimiter);
1419 // Remove the trailing delimiter and then recurse.
1420 String strippedTrail = path.substring(0, lastDelimiter);
1421 return getParentPath(strippedTrail);
1426 * Get the last element in a path that denotes the file or folder name.
1428 * @param path the provided path
1429 * @return the last element in the path
1431 protected String getLastElement(String path) {
1432 int lastDelimiter = path.lastIndexOf('/');
1433 if (lastDelimiter == -1)
1436 else if (lastDelimiter < path.length() - 1)
1437 // Return the part after the delimiter.
1438 return path.substring(lastDelimiter + 1);
1440 // Remove the trailing delimiter and then recurse.
1441 String strippedTrail = path.substring(0, lastDelimiter);
1442 return getLastElement(strippedTrail);
1447 * Only use the PathInfo for determining the requested path. If the
1448 * ServletPath is non-null, it will be because the WebDAV servlet has been
1449 * mapped to a URL other than /* to configure editing at different URL than
1452 * @param request the servlet request we are processing
1453 * @return the relative path
1455 protected String getRelativePath(HttpServletRequest request) {
1456 String result = request.getPathInfo();
1457 if (result == null || result.equals(""))
1464 * Return JAXP document builder instance.
1466 * @return the DocumentBuilder
1467 * @throws ServletException
1469 private DocumentBuilder getDocumentBuilder() throws ServletException {
1470 DocumentBuilder documentBuilder = null;
1471 DocumentBuilderFactory documentBuilderFactory = null;
1473 documentBuilderFactory = DocumentBuilderFactory.newInstance();
1474 documentBuilderFactory.setNamespaceAware(true);
1475 documentBuilderFactory.setExpandEntityReferences(false);
1476 documentBuilder = documentBuilderFactory.newDocumentBuilder();
1477 documentBuilder.setEntityResolver(new WebdavResolver(getServletContext()));
1478 } catch (ParserConfigurationException e) {
1479 throw new ServletException("Error while creating a document builder");
1481 return documentBuilder;
1485 * Generate the namespace declarations.
1487 * @return the namespace declarations
1489 private String generateNamespaceDeclarations() {
1490 return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
1494 * Propfind helper method. Dispays the properties of a lock-null resource.
1496 * @param req the HTTP request
1497 * @param resources Resources object associated with this context
1498 * @param generatedXML XML response to the Propfind request
1499 * @param path Path of the current resource
1500 * @param type Propfind type
1501 * @param propertiesVector If the propfind type is find properties by name,
1502 * then this Vector contains those properties
1504 private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector propertiesVector) {
1506 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1507 // (the "toUpperCase()" avoids problems on Windows systems)
1508 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF"))
1511 // Retrieving the lock associated with the lock-null resource
1512 LockInfo lock = resourceLocks.get(path);
1517 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1518 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1520 // Generating href element
1521 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1523 String absoluteUri = req.getRequestURI();
1524 String relativePath = getRelativePath(req);
1525 String toAppend = path.substring(relativePath.length());
1526 if (!toAppend.startsWith("/"))
1527 toAppend = "/" + toAppend;
1529 generatedXML.writeText(rewriteUrl(normalize(absoluteUri + toAppend)));
1531 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1533 String resourceName = path;
1534 int lastSlash = path.lastIndexOf('/');
1535 if (lastSlash != -1)
1536 resourceName = resourceName.substring(lastSlash + 1);
1542 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1543 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1545 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(lock.creationDate.getTime()));
1546 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1547 generatedXML.writeData(resourceName);
1548 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1549 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(lock.creationDate.getTime(), null));
1550 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(0));
1551 generatedXML.writeProperty(null, "getcontenttype", "");
1552 generatedXML.writeProperty(null, "getetag", "");
1553 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1554 generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
1555 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1557 generatedXML.writeProperty(null, "source", "");
1559 String supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1560 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1561 generatedXML.writeText(supportedLocks);
1562 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1564 generateLockDiscovery(path, generatedXML);
1566 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1567 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1568 generatedXML.writeText(status);
1569 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1570 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1574 case FIND_PROPERTY_NAMES:
1576 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1577 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1579 generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT);
1580 generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
1581 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1582 generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT);
1583 generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT);
1584 generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT);
1585 generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT);
1586 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1587 generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
1588 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
1590 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1591 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1592 generatedXML.writeText(status);
1593 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1594 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1598 case FIND_BY_PROPERTY:
1600 Vector<String> propertiesNotFound = new Vector<String>();
1602 // Parse the list of properties
1604 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1605 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1607 Enumeration properties = propertiesVector.elements();
1609 while (properties.hasMoreElements()) {
1611 String property = (String) properties.nextElement();
1613 if (property.equals("creationdate"))
1614 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(lock.creationDate.getTime()));
1615 else if (property.equals("displayname")) {
1616 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1617 generatedXML.writeData(resourceName);
1618 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1619 } else if (property.equals("getcontentlanguage"))
1620 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1621 else if (property.equals("getcontentlength"))
1622 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(0));
1623 else if (property.equals("getcontenttype"))
1624 generatedXML.writeProperty(null, "getcontenttype", "");
1625 else if (property.equals("getetag"))
1626 generatedXML.writeProperty(null, "getetag", "");
1627 else if (property.equals("getlastmodified"))
1628 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(lock.creationDate.getTime(), null));
1629 else if (property.equals("resourcetype")) {
1630 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1631 generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
1632 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1633 } else if (property.equals("source"))
1634 generatedXML.writeProperty(null, "source", "");
1635 else if (property.equals("supportedlock")) {
1636 supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1637 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1638 generatedXML.writeText(supportedLocks);
1639 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1640 } else if (property.equals("lockdiscovery")) {
1641 if (!generateLockDiscovery(path, generatedXML))
1642 propertiesNotFound.addElement(property);
1644 propertiesNotFound.addElement(property);
1648 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1649 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1650 generatedXML.writeText(status);
1651 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1652 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1654 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1656 if (propertiesNotFoundList.hasMoreElements()) {
1658 status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
1660 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1661 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1663 while (propertiesNotFoundList.hasMoreElements())
1664 generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
1666 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1667 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1668 generatedXML.writeText(status);
1669 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1670 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1678 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1683 * Return a context-relative path, beginning with a "/", that represents the
1684 * canonical version of the specified path after ".." and "." elements are
1685 * resolved out. If the specified path attempts to go outside the boundaries
1686 * of the current context (i.e. too many ".." path elements are present),
1687 * return <code>null</code> instead.
1689 * @param path Path to be normalized
1690 * @return the normalized path
1692 private String normalize(String path) {
1696 // Create a place for the normalized path
1697 String normalized = path;
1698 if (normalized.equals("/."))
1701 // Normalize the slashes and add leading slash if necessary
1702 if (normalized.indexOf('\\') >= 0)
1703 normalized = normalized.replace('\\', '/');
1704 if (!normalized.startsWith("/"))
1705 normalized = "/" + normalized;
1707 // Resolve occurrences of "//" in the normalized path
1709 int index = normalized.indexOf("//");
1712 normalized = normalized.substring(0, index) + normalized.substring(index + 1);
1715 // Resolve occurrences of "/./" in the normalized path
1717 int index = normalized.indexOf("/./");
1720 normalized = normalized.substring(0, index) + normalized.substring(index + 2);
1723 // Resolve occurrences of "/../" in the normalized path
1725 int index = normalized.indexOf("/../");
1729 return null; // Trying to go outside our context
1730 int index2 = normalized.lastIndexOf('/', index - 1);
1731 normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
1734 // Return the normalized path that we have completed
1740 * Propfind helper method.
1742 * @param req The servlet request
1743 * @param resources Resources object associated with this context
1744 * @param generatedXML XML response to the Propfind request
1745 * @param path Path of the current resource
1746 * @param type Propfind type
1747 * @param propertiesVector If the propfind type is find properties by name,
1748 * then this Vector contains those properties
1749 * @param resource the resource object
1751 private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector<String> propertiesVector, Object resource) {
1753 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1754 // (the "toUpperCase()" avoids problems on Windows systems)
1755 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF"))
1758 FolderDTO folder = null;
1759 FileHeaderDTO file = null;
1760 if (resource instanceof FolderDTO)
1761 folder = (FolderDTO) resource;
1763 file = (FileHeaderDTO) resource;
1764 // Retrieve the creation date.
1767 creation = folder.getAuditInfo().getCreationDate().getTime();
1769 creation = file.getAuditInfo().getCreationDate().getTime();
1770 // Retrieve the modification date.
1771 long modification = 0;
1773 modification = folder.getAuditInfo().getCreationDate().getTime();
1775 modification = file.getAuditInfo().getCreationDate().getTime();
1777 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1778 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1780 // Generating href element
1781 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1783 String href = req.getContextPath() + req.getServletPath();
1784 if (href.endsWith("/") && path.startsWith("/"))
1785 href += path.substring(1);
1788 if (folder != null && !href.endsWith("/"))
1791 generatedXML.writeText(rewriteUrl(href));
1793 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1795 String resourceName = path;
1796 int lastSlash = path.lastIndexOf('/');
1797 if (lastSlash != -1)
1798 resourceName = resourceName.substring(lastSlash + 1);
1804 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1805 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1807 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(creation));
1808 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1809 generatedXML.writeData(resourceName);
1810 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1812 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1813 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(file.getFileSize()));
1814 // XXX Once we properly store the MIME type in the file,
1815 // retrieve it from there.
1816 String contentType = getServletContext().getMimeType(file.getName());
1817 if (contentType != null)
1818 generatedXML.writeProperty(null, "getcontenttype", contentType);
1819 generatedXML.writeProperty(null, "getetag", getETag(file, null));
1820 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1822 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1823 generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
1824 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1827 generatedXML.writeProperty(null, "source", "");
1829 String supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1830 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1831 generatedXML.writeText(supportedLocks);
1832 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1834 generateLockDiscovery(path, generatedXML);
1836 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1837 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1838 generatedXML.writeText(status);
1839 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1840 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1844 case FIND_PROPERTY_NAMES:
1846 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1847 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1849 generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT);
1850 generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
1852 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1853 generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT);
1854 generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT);
1855 generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT);
1856 generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT);
1858 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1859 generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
1860 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
1862 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1863 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1864 generatedXML.writeText(status);
1865 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1866 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1870 case FIND_BY_PROPERTY:
1872 Vector<String> propertiesNotFound = new Vector<String>();
1874 // Parse the list of properties
1876 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1877 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1879 Enumeration<String> properties = propertiesVector.elements();
1881 while (properties.hasMoreElements()) {
1883 String property = properties.nextElement();
1885 if (property.equals("creationdate"))
1886 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(creation));
1887 else if (property.equals("displayname")) {
1888 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1889 generatedXML.writeData(resourceName);
1890 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1891 } else if (property.equals("getcontentlanguage")) {
1893 propertiesNotFound.addElement(property);
1895 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1896 } else if (property.equals("getcontentlength")) {
1898 propertiesNotFound.addElement(property);
1900 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(file.getFileSize()));
1901 } else if (property.equals("getcontenttype")) {
1903 propertiesNotFound.addElement(property);
1905 // XXX Once we properly store the MIME type in the
1907 // retrieve it from there.
1908 generatedXML.writeProperty(null, "getcontenttype", getServletContext().getMimeType(file.getName()));
1909 } else if (property.equals("getetag")) {
1911 propertiesNotFound.addElement(property);
1913 generatedXML.writeProperty(null, "getetag", getETag(file, null));
1914 } else if (property.equals("getlastmodified")) {
1916 propertiesNotFound.addElement(property);
1918 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1919 } else if (property.equals("resourcetype")) {
1920 if (folder != null) {
1921 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1922 generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
1923 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1925 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1926 } else if (property.equals("source"))
1927 generatedXML.writeProperty(null, "source", "");
1928 else if (property.equals("supportedlock")) {
1929 supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1930 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1931 generatedXML.writeText(supportedLocks);
1932 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1933 } else if (property.equals("lockdiscovery")) {
1934 if (!generateLockDiscovery(path, generatedXML))
1935 propertiesNotFound.addElement(property);
1937 propertiesNotFound.addElement(property);
1940 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1941 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1942 generatedXML.writeText(status);
1943 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1944 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1946 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1948 if (propertiesNotFoundList.hasMoreElements()) {
1950 status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
1952 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1953 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1955 while (propertiesNotFoundList.hasMoreElements())
1956 generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
1957 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1958 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1959 generatedXML.writeText(status);
1960 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1961 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1968 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1973 * Get the ETag associated with a file.
1975 * @param file the FileHeaderDTO object for this file
1976 * @param oldBody the old version of the file, if requested
1977 * @return a string containing the ETag
1979 protected String getETag(FileHeaderDTO file, FileBodyDTO oldBody) {
1980 if (oldBody == null)
1981 return "\"" + file.getFileSize() + "-" + file.getAuditInfo().getModificationDate().getTime() + "\"";
1982 return "\"" + oldBody.getFileSize() + "-" + oldBody.getAuditInfo().getModificationDate().getTime() + "\"";
1988 * @param path Path which has to be rewritten
1989 * @return the rewritten URL
1991 private String rewriteUrl(String path) {
1992 return urlEncoder.encode(path);
1996 * Print the lock discovery information associated with a path.
1999 * @param generatedXML XML data to which the locks info will be appended
2000 * @return true if at least one lock was displayed
2002 private boolean generateLockDiscovery(String path, XMLWriter generatedXML) {
2003 LockInfo resourceLock = resourceLocks.get(path);
2004 Enumeration collectionLocksList = collectionLocks.elements();
2005 boolean wroteStart = false;
2006 if (resourceLock != null) {
2008 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
2009 resourceLock.toXML(generatedXML);
2012 while (collectionLocksList.hasMoreElements()) {
2013 LockInfo currentLock = (LockInfo) collectionLocksList.nextElement();
2014 if (path.startsWith(currentLock.path)) {
2017 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
2019 currentLock.toXML(generatedXML);
2024 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING);
2033 * Get creation date in ISO format.
2035 * @param creationDate
2036 * @return the formatted date
2038 private String getISOCreationDate(long creationDate) {
2039 String dateValue = null;
2040 synchronized (creationDateFormat) {
2041 dateValue = creationDateFormat.format(new Date(creationDate));
2043 StringBuffer creationDateValue = new StringBuffer(dateValue);
2045 int offset = Calendar.getInstance().getTimeZone().getRawOffset()
2046 / 3600000; // FIXME ?
2048 creationDateValue.append("-");
2050 } else if (offset > 0) {
2051 creationDateValue.append("+");
2055 creationDateValue.append("0");
2056 creationDateValue.append(offset + ":00");
2058 creationDateValue.append("Z");
2061 return creationDateValue.toString();
2065 * Determines the methods normally allowed for the resource.
2067 * @param req the HTTP request
2068 * @return a list of the allowed methods
2069 * @throws RpcException if there is an error while communicating with the
2072 private StringBuffer determineMethodsAllowed(HttpServletRequest req) throws RpcException {
2073 StringBuffer methodsAllowed = new StringBuffer();
2074 boolean exists = true;
2075 Object object = null;
2076 User user = getUser(req);
2077 String path = getRelativePath(req);
2078 if (user == null && "/".equals(path))
2079 // Special case: OPTIONS request before authentication
2080 return new StringBuffer("OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND, PUT");
2082 object = getService().getResourceAtPath(user.getId(), path, true);
2083 } catch (ObjectNotFoundException e) {
2088 methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
2089 return methodsAllowed;
2092 methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
2093 methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
2094 methodsAllowed.append(", PROPFIND");
2096 if (!(object instanceof FolderDTO))
2097 methodsAllowed.append(", PUT");
2099 return methodsAllowed;
2103 * Check to see if a resource is currently write locked. The method will
2104 * look at the "If" header to make sure the client has given the appropriate
2107 * @param req the HTTP request
2108 * @return boolean true if the resource is locked (and no appropriate lock
2109 * token has been found for at least one of the non-shared locks
2110 * which are present on the resource).
2112 private boolean isLocked(HttpServletRequest req) {
2113 String path = getRelativePath(req);
2114 String ifHeader = req.getHeader("If");
2115 if (ifHeader == null)
2117 String lockTokenHeader = req.getHeader("Lock-Token");
2118 if (lockTokenHeader == null)
2119 lockTokenHeader = "";
2120 return isLocked(path, ifHeader + lockTokenHeader);
2124 * Check to see if a resource is currently write locked.
2126 * @param path Path of the resource
2127 * @param ifHeader "If" HTTP header which was included in the request
2128 * @return boolean true if the resource is locked (and no appropriate lock
2129 * token has been found for at least one of the non-shared locks
2130 * which are present on the resource).
2132 private boolean isLocked(String path, String ifHeader) {
2133 // Checking resource locks
2134 LockInfo lock = resourceLocks.get(path);
2135 Enumeration tokenList = null;
2136 if (lock != null && lock.hasExpired())
2137 resourceLocks.remove(path);
2138 else if (lock != null) {
2139 // At least one of the tokens of the locks must have been given
2140 tokenList = lock.tokens.elements();
2141 boolean tokenMatch = false;
2142 while (tokenList.hasMoreElements()) {
2143 String token = (String) tokenList.nextElement();
2144 if (ifHeader.indexOf(token) != -1)
2150 // Checking inheritable collection locks
2151 Enumeration collectionLocksList = collectionLocks.elements();
2152 while (collectionLocksList.hasMoreElements()) {
2153 lock = (LockInfo) collectionLocksList.nextElement();
2154 if (lock.hasExpired())
2155 collectionLocks.removeElement(lock);
2156 else if (path.startsWith(lock.path)) {
2157 tokenList = lock.tokens.elements();
2158 boolean tokenMatch = false;
2159 while (tokenList.hasMoreElements()) {
2160 String token = (String) tokenList.nextElement();
2161 if (ifHeader.indexOf(token) != -1)
2172 * Parse the content-range header.
2174 * @param request The servlet request we are processing
2175 * @param response The servlet response we are creating
2177 * @throws IOException
2179 protected Range parseContentRange(HttpServletRequest request, HttpServletResponse response) throws IOException {
2181 // Retrieving the content-range header (if any is specified
2182 String rangeHeader = request.getHeader("Content-Range");
2184 if (rangeHeader == null)
2187 // bytes is the only range unit supported
2188 if (!rangeHeader.startsWith("bytes")) {
2189 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2193 rangeHeader = rangeHeader.substring(6).trim();
2195 int dashPos = rangeHeader.indexOf('-');
2196 int slashPos = rangeHeader.indexOf('/');
2198 if (dashPos == -1) {
2199 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2203 if (slashPos == -1) {
2204 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2208 Range range = new Range();
2211 range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
2212 range.end = Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
2213 range.length = Long.parseLong(rangeHeader.substring(slashPos + 1, rangeHeader.length()));
2214 } catch (NumberFormatException e) {
2215 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2219 if (!range.validate()) {
2220 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2229 * Handle a partial PUT. New content specified in request is appended to
2230 * existing content in oldRevisionContent (if present). This code does not
2231 * support simultaneous partial updates to the same resource.
2237 * @throws IOException
2238 * @throws RpcException
2239 * @throws InsufficientPermissionsException
2240 * @throws ObjectNotFoundException
2242 protected File executePartialPut(HttpServletRequest req, Range range, String path) throws IOException, RpcException, ObjectNotFoundException, InsufficientPermissionsException {
2243 // Append data specified in ranges to existing content for this
2244 // resource - create a temporary file on the local file system to
2245 // perform this operation.
2246 File tempDir = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
2247 // Convert all '/' characters to '.' in resourcePath
2248 String convertedResourcePath = path.replace('/', '.');
2249 File contentFile = new File(tempDir, convertedResourcePath);
2250 if (contentFile.createNewFile())
2251 // Clean up contentFile when Tomcat is terminated.
2252 contentFile.deleteOnExit();
2254 RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw");
2256 User user = getUser(req);
2257 User owner = getOwner(req);
2258 FileHeaderDTO oldResource = null;
2260 Object obj = getService().getResourceAtPath(owner.getId(), path, true);
2261 if (obj instanceof FileHeaderDTO)
2262 oldResource = (FileHeaderDTO) obj;
2263 } catch (ObjectNotFoundException e) {
2267 // Copy data in oldRevisionContent to contentFile
2268 if (oldResource != null) {
2269 InputStream contents = getService().getFileContents(user.getId(), oldResource.getId());
2270 BufferedInputStream bufOldRevStream = new BufferedInputStream(contents, BUFFER_SIZE);
2273 byte[] copyBuffer = new byte[BUFFER_SIZE];
2274 while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1)
2275 randAccessContentFile.write(copyBuffer, 0, numBytesRead);
2277 bufOldRevStream.close();
2280 randAccessContentFile.setLength(range.length);
2282 // Append data in request input stream to contentFile
2283 randAccessContentFile.seek(range.start);
2285 byte[] transferBuffer = new byte[BUFFER_SIZE];
2286 BufferedInputStream requestBufInStream = new BufferedInputStream(req.getInputStream(), BUFFER_SIZE);
2287 while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1)
2288 randAccessContentFile.write(transferBuffer, 0, numBytesRead);
2289 randAccessContentFile.close();
2290 requestBufInStream.close();
2297 * Serve the specified resource, optionally including the data content.
2299 * @param req The servlet request we are processing
2300 * @param resp The servlet response we are creating
2301 * @param content Should the content be included?
2302 * @exception IOException if an input/output error occurs
2303 * @exception ServletException if a servlet-specified error occurs
2304 * @throws RpcException
2305 * @throws InsufficientPermissionsException
2306 * @throws ObjectNotFoundException
2308 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException, ServletException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2310 // Identify the requested resource path
2311 String path = getRelativePath(req);
2312 if (logger.isDebugEnabled())
2314 logger.debug("Serving resource '" + path + "' headers and data");
2316 logger.debug("Serving resource '" + path + "' headers only");
2318 User user = getUser(req);
2319 boolean exists = true;
2320 Object resource = null;
2321 FileHeaderDTO file = null;
2322 FolderDTO folder = null;
2324 resource = getService().getResourceAtPath(user.getId(), path, true);
2325 } catch (ObjectNotFoundException e) {
2327 } catch (RpcException e) {
2328 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
2333 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
2337 if (resource instanceof FolderDTO)
2338 folder = (FolderDTO) resource;
2340 file = (FileHeaderDTO) resource;
2342 // If the resource is not a collection, and the resource path
2343 // ends with "/" or "\", return NOT FOUND
2345 if (path.endsWith("/") || path.endsWith("\\")) {
2346 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
2350 // Check if the conditions specified in the optional If headers are
2353 // Checking If headers
2354 if (!checkIfHeaders(req, resp, file, null))
2357 // Find content type.
2358 String contentType = null;
2360 contentType = file.getMimeType();
2361 if (contentType == null) {
2362 contentType = getServletContext().getMimeType(file.getName());
2363 file.setMimeType(contentType);
2366 contentType = "text/html;charset=UTF-8";
2368 ArrayList ranges = null;
2369 long contentLength = -1L;
2372 // Parse range specifier
2373 ranges = parseRange(req, resp, file, null);
2375 resp.setHeader("ETag", getETag(file, null));
2376 // Last-Modified header
2377 resp.setHeader("Last-Modified", getLastModifiedHttp(file.getAuditInfo()));
2378 // Get content length
2379 contentLength = file.getFileSize();
2380 // Special case for zero length files, which would cause a
2381 // (silent) ISE when setting the output buffer size
2382 if (contentLength == 0L)
2386 ServletOutputStream ostream = null;
2387 PrintWriter writer = null;
2391 ostream = resp.getOutputStream();
2392 } catch (IllegalStateException e) {
2393 // If it fails, we try to get a Writer instead if we're
2394 // trying to serve a text file
2395 if (contentType == null || contentType.startsWith("text") || contentType.endsWith("xml"))
2396 writer = resp.getWriter();
2401 if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
2402 // Set the appropriate output headers
2403 if (contentType != null) {
2404 if (logger.isDebugEnabled())
2405 logger.debug("DefaultServlet.serveFile: contentType='" + contentType + "'");
2406 resp.setContentType(contentType);
2408 if (file != null && contentLength >= 0) {
2409 if (logger.isDebugEnabled())
2410 logger.debug("DefaultServlet.serveFile: contentLength=" + contentLength);
2411 if (contentLength < Integer.MAX_VALUE)
2412 resp.setContentLength((int) contentLength);
2414 // Set the content-length as String to be able to use a long
2415 resp.setHeader("content-length", "" + contentLength);
2418 InputStream renderResult = null;
2421 // Serve the directory browser
2422 renderResult = renderHtml(req.getContextPath(), path, folder, req);
2424 // Copy the input stream to our output stream (if requested)
2427 resp.setBufferSize(output);
2428 } catch (IllegalStateException e) {
2431 if (ostream != null)
2432 copy(file, renderResult, ostream, req, null);
2434 copy(file, renderResult, writer, req, null);
2437 if (ranges == null || ranges.isEmpty())
2439 // Partial content response.
2440 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
2442 if (ranges.size() == 1) {
2443 Range range = (Range) ranges.get(0);
2444 resp.addHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + range.length);
2445 long length = range.end - range.start + 1;
2446 if (length < Integer.MAX_VALUE)
2447 resp.setContentLength((int) length);
2449 // Set the content-length as String to be able to use a long
2450 resp.setHeader("content-length", "" + length);
2452 if (contentType != null) {
2453 if (logger.isDebugEnabled())
2454 logger.debug("DefaultServlet.serveFile: contentType='" + contentType + "'");
2455 resp.setContentType(contentType);
2460 resp.setBufferSize(output);
2461 } catch (IllegalStateException e) {
2464 if (ostream != null)
2465 copy(file, ostream, range, req, null);
2467 copy(file, writer, range, req, null);
2472 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
2476 resp.setBufferSize(output);
2477 } catch (IllegalStateException e) {
2480 if (ostream != null)
2481 copy(file, ostream, ranges.iterator(), contentType, req, null);
2483 copy(file, writer, ranges.iterator(), contentType, req, null);
2493 * Retrieve the last modified date of a resource in HTTP format.
2495 * @param auditInfo the audit info for the specified resource
2496 * @return the last modified date in HTTP format
2498 protected String getLastModifiedHttp(AuditInfoDTO auditInfo) {
2499 Date modifiedDate = auditInfo.getModificationDate();
2500 if (modifiedDate == null)
2501 modifiedDate = auditInfo.getCreationDate();
2502 if (modifiedDate == null)
2503 modifiedDate = new Date();
2504 String lastModifiedHttp = null;
2505 synchronized (format) {
2506 lastModifiedHttp = format.format(modifiedDate);
2508 return lastModifiedHttp;
2512 * Parse the range header.
2514 * @param request The servlet request we are processing
2515 * @param response The servlet response we are creating
2517 * @param oldBody the old version of the file, if requested
2518 * @return Vector of ranges
2519 * @throws IOException
2521 protected ArrayList parseRange(HttpServletRequest request, HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2522 // Checking If-Range
2523 String headerValue = request.getHeader("If-Range");
2524 if (headerValue != null) {
2525 long headerValueTime = -1L;
2527 headerValueTime = request.getDateHeader("If-Range");
2528 } catch (IllegalArgumentException e) {
2532 String eTag = getETag(file, oldBody);
2533 long lastModified = oldBody == null ?
2534 file.getAuditInfo().getModificationDate().getTime() :
2535 oldBody.getAuditInfo().getModificationDate().getTime();
2537 if (headerValueTime == -1L) {
2538 // If the ETag the client gave does not match the entity
2539 // etag, then the entire entity is returned.
2540 if (!eTag.equals(headerValue.trim()))
2543 // If the timestamp of the entity the client got is older than
2544 // the last modification date of the entity, the entire entity
2546 if (lastModified > headerValueTime + 1000)
2550 long fileLength = oldBody == null ? file.getFileSize() : oldBody.getFileSize();
2551 if (fileLength == 0)
2554 // Retrieving the range header (if any is specified).
2555 String rangeHeader = request.getHeader("Range");
2557 if (rangeHeader == null)
2559 // bytes is the only range unit supported (and I don't see the point
2560 // of adding new ones).
2561 if (!rangeHeader.startsWith("bytes")) {
2562 response.addHeader("Content-Range", "bytes */" + fileLength);
2563 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2567 rangeHeader = rangeHeader.substring(6);
2569 // Vector that will contain all the ranges which are successfully
2571 ArrayList<Range> result = new ArrayList<Range>();
2572 StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
2573 // Parsing the range list
2574 while (commaTokenizer.hasMoreTokens()) {
2575 String rangeDefinition = commaTokenizer.nextToken().trim();
2577 Range currentRange = new Range();
2578 currentRange.length = fileLength;
2580 int dashPos = rangeDefinition.indexOf('-');
2582 if (dashPos == -1) {
2583 response.addHeader("Content-Range", "bytes */" + fileLength);
2584 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2590 long offset = Long.parseLong(rangeDefinition);
2591 currentRange.start = fileLength + offset;
2592 currentRange.end = fileLength - 1;
2593 } catch (NumberFormatException e) {
2594 response.addHeader("Content-Range", "bytes */" + fileLength);
2595 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2600 currentRange.start = Long.parseLong(rangeDefinition.substring(0, dashPos));
2601 if (dashPos < rangeDefinition.length() - 1)
2602 currentRange.end = Long.parseLong(rangeDefinition.substring(dashPos + 1, rangeDefinition.length()));
2604 currentRange.end = fileLength - 1;
2605 } catch (NumberFormatException e) {
2606 response.addHeader("Content-Range", "bytes */" + fileLength);
2607 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2611 if (!currentRange.validate()) {
2612 response.addHeader("Content-Range", "bytes */" + fileLength);
2613 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2616 result.add(currentRange);
2622 * Check if the conditions specified in the optional If headers are
2625 * @param request The servlet request we are processing
2626 * @param response The servlet response we are creating
2627 * @param file the file resource against which the checks will be made
2628 * @param oldBody the old version of the file, if requested
2629 * @return boolean true if the resource meets all the specified conditions,
2630 * and false if any of the conditions is not satisfied, in which
2631 * case request processing is stopped
2632 * @throws IOException
2634 protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response,
2635 FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2636 // TODO : Checking the WebDAV If header
2637 return checkIfMatch(request, response, file, oldBody) &&
2638 checkIfModifiedSince(request, response, file, oldBody) &&
2639 checkIfNoneMatch(request, response, file, oldBody) &&
2640 checkIfUnmodifiedSince(request, response, file, oldBody);
2644 * Check if the if-match condition is satisfied.
2646 * @param request The servlet request we are processing
2647 * @param response The servlet response we are creating
2648 * @param file the file object
2649 * @param oldBody the old version of the file, if requested
2650 * @return boolean true if the resource meets the specified condition, and
2651 * false if the condition is not satisfied, in which case request
2652 * processing is stopped
2653 * @throws IOException
2655 private boolean checkIfMatch(HttpServletRequest request, HttpServletResponse response,
2656 FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2657 String eTag = getETag(file, oldBody);
2658 String headerValue = request.getHeader("If-Match");
2659 if (headerValue != null)
2660 if (headerValue.indexOf('*') == -1) {
2661 StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2662 boolean conditionSatisfied = false;
2663 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2664 String currentToken = commaTokenizer.nextToken();
2665 if (currentToken.trim().equals(eTag))
2666 conditionSatisfied = true;
2668 // If none of the given ETags match, 412 Precodition failed is
2670 if (!conditionSatisfied) {
2671 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2679 * Check if the if-modified-since condition is satisfied.
2681 * @param request The servlet request we are processing
2682 * @param response The servlet response we are creating
2683 * @param file the file object
2684 * @param oldBody the old version of the file, if requested
2685 * @return boolean true if the resource meets the specified condition, and
2686 * false if the condition is not satisfied, in which case request
2687 * processing is stopped
2689 private boolean checkIfModifiedSince(HttpServletRequest request,
2690 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody) {
2692 long headerValue = request.getDateHeader("If-Modified-Since");
2693 long lastModified = oldBody == null ?
2694 file.getAuditInfo().getModificationDate().getTime() :
2695 oldBody.getAuditInfo().getModificationDate().getTime();
2696 if (headerValue != -1)
2697 // If an If-None-Match header has been specified, if modified
2700 if (request.getHeader("If-None-Match") == null && lastModified < headerValue + 1000) {
2701 // The entity has not been modified since the date
2702 // specified by the client. This is not an error case.
2703 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2704 response.setHeader("ETag", getETag(file, oldBody));
2707 } catch (IllegalArgumentException illegalArgument) {
2714 * Check if the if-none-match condition is satisfied.
2716 * @param request The servlet request we are processing
2717 * @param response The servlet response we are creating
2718 * @param file the file object
2719 * @param oldBody the old version of the file, if requested
2720 * @return boolean true if the resource meets the specified condition, and
2721 * false if the condition is not satisfied, in which case request
2722 * processing is stopped
2723 * @throws IOException
2725 private boolean checkIfNoneMatch(HttpServletRequest request,
2726 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody)
2727 throws IOException {
2728 String eTag = getETag(file, oldBody);
2729 String headerValue = request.getHeader("If-None-Match");
2730 if (headerValue != null) {
2731 boolean conditionSatisfied = false;
2732 if (!headerValue.equals("*")) {
2733 StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2734 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2735 String currentToken = commaTokenizer.nextToken();
2736 if (currentToken.trim().equals(eTag))
2737 conditionSatisfied = true;
2740 conditionSatisfied = true;
2741 if (conditionSatisfied) {
2742 // For GET and HEAD, we should respond with 304 Not Modified.
2743 // For every other method, 412 Precondition Failed is sent
2745 if ("GET".equals(request.getMethod()) || "HEAD".equals(request.getMethod())) {
2746 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2747 response.setHeader("ETag", getETag(file, oldBody));
2750 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2758 * Check if the if-unmodified-since condition is satisfied.
2760 * @param request The servlet request we are processing
2761 * @param response The servlet response we are creating
2762 * @param file the file object
2763 * @param oldBody the old version of the file, if requested
2764 * @return boolean true if the resource meets the specified condition, and
2765 * false if the condition is not satisfied, in which case request
2766 * processing is stopped
2767 * @throws IOException
2769 private boolean checkIfUnmodifiedSince(HttpServletRequest request,
2770 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody)
2771 throws IOException {
2773 long lastModified = oldBody == null ?
2774 file.getAuditInfo().getModificationDate().getTime() :
2775 oldBody.getAuditInfo().getModificationDate().getTime();
2776 long headerValue = request.getDateHeader("If-Unmodified-Since");
2777 if (headerValue != -1)
2778 if (lastModified >= headerValue + 1000) {
2779 // The entity has not been modified since the date
2780 // specified by the client. This is not an error case.
2781 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2784 } catch (IllegalArgumentException illegalArgument) {
2791 * Copy the contents of the specified input stream to the specified output
2792 * stream, and ensure that both streams are closed before returning (even in
2793 * the face of an exception).
2795 * @param file the file resource
2797 * @param ostream The output stream to write to
2798 * @param req the HTTP request
2799 * @param oldBody the old version of the file, if requested
2800 * @exception IOException if an input/output error occurs
2801 * @throws RpcException
2802 * @throws InsufficientPermissionsException
2803 * @throws ObjectNotFoundException
2805 protected void copy(FileHeaderDTO file, InputStream is, ServletOutputStream ostream,
2806 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
2807 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2808 IOException exception = null;
2809 InputStream resourceInputStream = null;
2810 User user = getUser(req);
2811 // Files open for all will not have specified a calling user in the request.
2813 user = getOwner(req);
2815 throw new ObjectNotFoundException("No user or owner specified");
2817 resourceInputStream = oldBody == null ?
2818 getService().getFileContents(user.getId(), file.getId()) :
2819 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2821 resourceInputStream = is;
2823 InputStream istream = new BufferedInputStream(resourceInputStream, input);
2824 // Copy the input stream to the output stream
2825 exception = copyRange(istream, ostream);
2826 // Clean up the input stream
2828 // Rethrow any exception that has occurred
2829 if (exception != null)
2834 * Copy the contents of the specified input stream to the specified output
2835 * stream, and ensure that both streams are closed before returning (even in
2836 * the face of an exception).
2838 * @param istream The input stream to read from
2839 * @param ostream The output stream to write to
2840 * @return Exception which occurred during processing
2842 private IOException copyRange(InputStream istream, ServletOutputStream ostream) {
2843 // Copy the input stream to the output stream
2844 IOException exception = null;
2845 byte buffer[] = new byte[input];
2846 int len = buffer.length;
2849 len = istream.read(buffer);
2852 ostream.write(buffer, 0, len);
2853 } catch (IOException e) {
2862 * Copy the contents of the specified input stream to the specified output
2863 * stream, and ensure that both streams are closed before returning (even in
2864 * the face of an exception).
2868 * @param resourceInfo The resource info
2869 * @param writer The writer to write to
2870 * @param req the HTTP request
2871 * @param oldBody the old version of the file, if requested
2872 * @exception IOException if an input/output error occurs
2873 * @throws RpcException
2874 * @throws InsufficientPermissionsException
2875 * @throws ObjectNotFoundException
2877 protected void copy(FileHeaderDTO file, InputStream is, PrintWriter writer,
2878 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
2879 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2880 IOException exception = null;
2882 User user = getUser(req);
2883 InputStream resourceInputStream = null;
2885 resourceInputStream = oldBody == null ?
2886 getService().getFileContents(user.getId(), file.getId()) :
2887 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2889 resourceInputStream = is;
2892 if (fileEncoding == null)
2893 reader = new InputStreamReader(resourceInputStream);
2895 reader = new InputStreamReader(resourceInputStream, fileEncoding);
2897 // Copy the input stream to the output stream
2898 exception = copyRange(reader, writer);
2899 // Clean up the reader
2901 // Rethrow any exception that has occurred
2902 if (exception != null)
2907 * Copy the contents of the specified input stream to the specified output
2908 * stream, and ensure that both streams are closed before returning (even in
2909 * the face of an exception).
2911 * @param reader The reader to read from
2912 * @param writer The writer to write to
2913 * @return Exception which occurred during processing
2915 private IOException copyRange(Reader reader, PrintWriter writer) {
2916 // Copy the input stream to the output stream
2917 IOException exception = null;
2918 char buffer[] = new char[input];
2919 int len = buffer.length;
2922 len = reader.read(buffer);
2925 writer.write(buffer, 0, len);
2926 } catch (IOException e) {
2935 * Copy the contents of the specified input stream to the specified output
2936 * stream, and ensure that both streams are closed before returning (even in
2937 * the face of an exception).
2940 * @param writer The writer to write to
2941 * @param ranges Enumeration of the ranges the client wanted to retrieve
2942 * @param contentType Content type of the resource
2943 * @param req the HTTP request
2944 * @param oldBody the old version of the file, if requested
2945 * @exception IOException if an input/output error occurs
2946 * @throws RpcException
2947 * @throws InsufficientPermissionsException
2948 * @throws ObjectNotFoundException
2950 protected void copy(FileHeaderDTO file, PrintWriter writer, Iterator ranges,
2951 String contentType, HttpServletRequest req, FileBodyDTO oldBody)
2952 throws IOException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2953 User user = getUser(req);
2954 IOException exception = null;
2955 while (exception == null && ranges.hasNext()) {
2956 InputStream resourceInputStream = oldBody == null ?
2957 getService().getFileContents(user.getId(), file.getId()) :
2958 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2960 if (fileEncoding == null)
2961 reader = new InputStreamReader(resourceInputStream);
2963 reader = new InputStreamReader(resourceInputStream, fileEncoding);
2964 Range currentRange = (Range) ranges.next();
2965 // Writing MIME header.
2967 writer.println("--" + mimeSeparation);
2968 if (contentType != null)
2969 writer.println("Content-Type: " + contentType);
2970 writer.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
2973 exception = copyRange(reader, writer, currentRange.start, currentRange.end);
2977 writer.print("--" + mimeSeparation + "--");
2978 // Rethrow any exception that has occurred
2979 if (exception != null)
2984 * Copy the contents of the specified input stream to the specified output
2985 * stream, and ensure that both streams are closed before returning (even in
2986 * the face of an exception).
2988 * @param istream The input stream to read from
2989 * @param ostream The output stream to write to
2990 * @param start Start of the range which will be copied
2991 * @param end End of the range which will be copied
2992 * @return Exception which occurred during processing
2994 private IOException copyRange(InputStream istream, ServletOutputStream ostream, long start, long end) {
2995 if (logger.isDebugEnabled())
2996 logger.debug("Serving bytes:" + start + "-" + end);
2998 istream.skip(start);
2999 } catch (IOException e) {
3002 IOException exception = null;
3003 long bytesToRead = end - start + 1;
3004 byte buffer[] = new byte[input];
3005 int len = buffer.length;
3006 while (bytesToRead > 0 && len >= buffer.length) {
3008 len = istream.read(buffer);
3009 if (bytesToRead >= len) {
3010 ostream.write(buffer, 0, len);
3013 ostream.write(buffer, 0, (int) bytesToRead);
3016 } catch (IOException e) {
3020 if (len < buffer.length)
3027 * Copy the contents of the specified input stream to the specified output
3028 * stream, and ensure that both streams are closed before returning (even in
3029 * the face of an exception).
3031 * @param reader The reader to read from
3032 * @param writer The writer to write to
3033 * @param start Start of the range which will be copied
3034 * @param end End of the range which will be copied
3035 * @return Exception which occurred during processing
3037 private IOException copyRange(Reader reader, PrintWriter writer, long start, long end) {
3040 } catch (IOException e) {
3043 IOException exception = null;
3044 long bytesToRead = end - start + 1;
3045 char buffer[] = new char[input];
3046 int len = buffer.length;
3047 while (bytesToRead > 0 && len >= buffer.length) {
3049 len = reader.read(buffer);
3050 if (bytesToRead >= len) {
3051 writer.write(buffer, 0, len);
3054 writer.write(buffer, 0, (int) bytesToRead);
3057 } catch (IOException e) {
3061 if (len < buffer.length)
3068 * Copy the contents of the specified input stream to the specified output
3069 * stream, and ensure that both streams are closed before returning (even in
3070 * the face of an exception).
3073 * @param ostream The output stream to write to
3074 * @param range Range the client wanted to retrieve
3075 * @param req the HTTP request
3076 * @param oldBody the old version of the file, if requested
3077 * @exception IOException if an input/output error occurs
3078 * @throws RpcException
3079 * @throws InsufficientPermissionsException
3080 * @throws ObjectNotFoundException
3082 protected void copy(FileHeaderDTO file, ServletOutputStream ostream, Range range,
3083 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
3084 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
3085 IOException exception = null;
3086 User user = getUser(req);
3087 InputStream resourceInputStream = oldBody == null ?
3088 getService().getFileContents(user.getId(), file.getId()) :
3089 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3090 InputStream istream = new BufferedInputStream(resourceInputStream, input);
3091 exception = copyRange(istream, ostream, range.start, range.end);
3092 // Clean up the input stream
3094 // Rethrow any exception that has occurred
3095 if (exception != null)
3100 * Copy the contents of the specified input stream to the specified output
3101 * stream, and ensure that both streams are closed before returning (even in
3102 * the face of an exception).
3105 * @param writer The writer to write to
3106 * @param range Range the client wanted to retrieve
3107 * @param req the HTTP request
3108 * @param oldBody the old version of the file, if requested
3109 * @exception IOException if an input/output error occurs
3110 * @throws RpcException
3111 * @throws InsufficientPermissionsException
3112 * @throws ObjectNotFoundException
3114 protected void copy(FileHeaderDTO file, PrintWriter writer, Range range,
3115 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
3116 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
3117 IOException exception = null;
3118 User user = getUser(req);
3119 InputStream resourceInputStream = oldBody == null ?
3120 getService().getFileContents(user.getId(), file.getId()) :
3121 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3123 if (fileEncoding == null)
3124 reader = new InputStreamReader(resourceInputStream);
3126 reader = new InputStreamReader(resourceInputStream, fileEncoding);
3128 exception = copyRange(reader, writer, range.start, range.end);
3129 // Clean up the input stream
3131 // Rethrow any exception that has occurred
3132 if (exception != null)
3137 * Copy the contents of the specified input stream to the specified output
3138 * stream, and ensure that both streams are closed before returning (even in
3139 * the face of an exception).
3142 * @param ostream The output stream to write to
3143 * @param ranges Enumeration of the ranges the client wanted to retrieve
3144 * @param contentType Content type of the resource
3145 * @param req the HTTP request
3146 * @param oldBody the old version of the file, if requested
3147 * @exception IOException if an input/output error occurs
3148 * @throws RpcException
3149 * @throws InsufficientPermissionsException
3150 * @throws ObjectNotFoundException
3152 protected void copy(FileHeaderDTO file, ServletOutputStream ostream,
3153 Iterator ranges, String contentType, HttpServletRequest req,
3154 FileBodyDTO oldBody) throws IOException, ObjectNotFoundException,
3155 InsufficientPermissionsException, RpcException {
3156 IOException exception = null;
3157 User user = getUser(req);
3158 while (exception == null && ranges.hasNext()) {
3159 InputStream resourceInputStream = oldBody == null ?
3160 getService().getFileContents(user.getId(), file.getId()) :
3161 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3162 InputStream istream = new BufferedInputStream(resourceInputStream, input);
3163 Range currentRange = (Range) ranges.next();
3164 // Writing MIME header.
3166 ostream.println("--" + mimeSeparation);
3167 if (contentType != null)
3168 ostream.println("Content-Type: " + contentType);
3169 ostream.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
3173 exception = copyRange(istream, ostream, currentRange.start, currentRange.end);
3178 ostream.print("--" + mimeSeparation + "--");
3179 // Rethrow any exception that has occurred
3180 if (exception != null)
3185 * Return an InputStream to an HTML representation of the contents of this
3188 * @param contextPath Context path to which our internal paths are relative
3189 * @param path the requested path to the resource
3190 * @param folder the specified directory
3191 * @param req the HTTP request
3192 * @return an input stream with the rendered contents
3193 * @throws IOException
3194 * @throws ServletException
3196 private InputStream renderHtml(String contextPath, String path, FolderDTO folder, HttpServletRequest req) throws IOException, ServletException {
3197 String name = folder.getName();
3198 // Prepare a writer to a buffered area
3199 ByteArrayOutputStream stream = new ByteArrayOutputStream();
3200 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
3201 PrintWriter writer = new PrintWriter(osWriter);
3202 StringBuffer sb = new StringBuffer();
3203 // rewriteUrl(contextPath) is expensive. cache result for later reuse
3204 String rewrittenContextPath = rewriteUrl(contextPath);
3205 // Render the page header
3206 sb.append("<html>\r\n");
3207 sb.append("<head>\r\n");
3208 sb.append("<title>");
3209 sb.append("Index of " + name);
3210 sb.append("</title>\r\n");
3211 sb.append("<STYLE><!--");
3213 sb.append("--></STYLE> ");
3214 sb.append("</head>\r\n");
3215 sb.append("<body>");
3217 sb.append("Index of " + name);
3219 // Render the link to our parent (if required)
3220 String parentDirectory = path;
3221 if (parentDirectory.endsWith("/"))
3222 parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
3223 int slash = parentDirectory.lastIndexOf('/');
3225 String parent = path.substring(0, slash);
3226 sb.append(" - <a href=\"");
3227 sb.append(rewrittenContextPath);
3228 if (parent.equals(""))
3230 sb.append(rewriteUrl(parent));
3231 if (!parent.endsWith("/"))
3235 sb.append("Up To " + parent);
3241 sb.append("<HR size=\"1\" noshade=\"noshade\">");
3243 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
3245 // Render the column headings
3246 sb.append("<tr>\r\n");
3247 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
3249 sb.append("</strong></font></td>\r\n");
3250 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
3252 sb.append("</strong></font></td>\r\n");
3253 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
3254 sb.append("Last modified");
3255 sb.append("</strong></font></td>\r\n");
3257 // Render the directory entries within this directory
3258 boolean shade = false;
3259 Iterator iter = folder.getSubfolders().iterator();
3260 while (iter.hasNext()) {
3261 FolderDTO subf = (FolderDTO) iter.next();
3262 String resourceName = subf.getName();
3263 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
3268 sb.append(" bgcolor=\"#eeeeee\"");
3272 sb.append("<td align=\"left\"> \r\n");
3273 sb.append("<a href=\"");
3274 sb.append(rewrittenContextPath);
3275 sb.append(rewriteUrl(path + resourceName));
3277 sb.append("\"><tt>");
3278 sb.append(RequestUtil.filter(resourceName));
3280 sb.append("</tt></a></td>\r\n");
3282 sb.append("<td align=\"right\"><tt>");
3283 sb.append(" ");
3284 sb.append("</tt></td>\r\n");
3286 sb.append("<td align=\"right\"><tt>");
3287 sb.append(getLastModifiedHttp(folder.getAuditInfo()));
3288 sb.append("</tt></td>\r\n");
3290 sb.append("</tr>\r\n");
3292 List<FileHeaderDTO> files;
3294 User user = getUser(req);
3295 files = getService().getFiles(user.getId(), folder.getId(), true);
3296 } catch (ObjectNotFoundException e) {
3297 throw new ServletException(e.getMessage());
3298 } catch (InsufficientPermissionsException e) {
3299 throw new ServletException(e.getMessage());
3300 } catch (RpcException e) {
3301 throw new ServletException(e.getMessage());
3303 for (FileHeaderDTO file : files) {
3304 String resourceName = file.getName();
3305 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
3310 sb.append(" bgcolor=\"#eeeeee\"");
3314 sb.append("<td align=\"left\"> \r\n");
3315 sb.append("<a href=\"");
3316 sb.append(rewrittenContextPath);
3317 sb.append(rewriteUrl(path + resourceName));
3318 sb.append("\"><tt>");
3319 sb.append(RequestUtil.filter(resourceName));
3320 sb.append("</tt></a></td>\r\n");
3322 sb.append("<td align=\"right\"><tt>");
3323 sb.append(renderSize(file.getFileSize()));
3324 sb.append("</tt></td>\r\n");
3326 sb.append("<td align=\"right\"><tt>");
3327 sb.append(getLastModifiedHttp(file.getAuditInfo()));
3328 sb.append("</tt></td>\r\n");
3330 sb.append("</tr>\r\n");
3333 // Render the page footer
3334 sb.append("</table>\r\n");
3336 sb.append("<HR size=\"1\" noshade=\"noshade\">");
3338 sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
3339 sb.append("</body>\r\n");
3340 sb.append("</html>\r\n");
3342 // Return an input stream to the underlying bytes
3343 writer.write(sb.toString());
3345 return new ByteArrayInputStream(stream.toByteArray());
3350 * Render the specified file size (in bytes).
3352 * @param size File size (in bytes)
3353 * @return the size as a string
3355 private String renderSize(long size) {
3356 long leftSide = size / 1024;
3357 long rightSide = size % 1024 / 103; // Makes 1 digit
3358 if (leftSide == 0 && rightSide == 0 && size > 0)
3360 return "" + leftSide + "." + rightSide + " kb";
3366 * @param req Servlet request
3367 * @param resp Servlet response
3368 * @return boolean true if the copy is successful
3369 * @throws IOException
3371 private boolean copyResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
3372 // Parsing destination header
3373 String destinationPath = req.getHeader("Destination");
3374 if (destinationPath == null) {
3375 resp.sendError(WebdavStatus.SC_BAD_REQUEST);
3379 // Remove url encoding from destination
3380 destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
3382 int protocolIndex = destinationPath.indexOf("://");
3383 if (protocolIndex >= 0) {
3384 // if the Destination URL contains the protocol, we can safely
3385 // trim everything upto the first "/" character after "://"
3386 int firstSeparator = destinationPath.indexOf("/", protocolIndex + 4);
3387 if (firstSeparator < 0)
3388 destinationPath = "/";
3390 destinationPath = destinationPath.substring(firstSeparator);
3392 String hostName = req.getServerName();
3393 if (hostName != null && destinationPath.startsWith(hostName))
3394 destinationPath = destinationPath.substring(hostName.length());
3396 int portIndex = destinationPath.indexOf(":");
3398 destinationPath = destinationPath.substring(portIndex);
3400 if (destinationPath.startsWith(":")) {
3401 int firstSeparator = destinationPath.indexOf("/");
3402 if (firstSeparator < 0)
3403 destinationPath = "/";
3405 destinationPath = destinationPath.substring(firstSeparator);
3409 // Normalise destination path (remove '.' and '..')
3410 destinationPath = normalize(destinationPath);
3412 String contextPath = req.getContextPath();
3413 if (contextPath != null && destinationPath.startsWith(contextPath))
3414 destinationPath = destinationPath.substring(contextPath.length());
3416 String pathInfo = req.getPathInfo();
3417 if (pathInfo != null) {
3418 String servletPath = req.getServletPath();
3419 if (servletPath != null && destinationPath.startsWith(servletPath))
3420 destinationPath = destinationPath.substring(servletPath.length());
3423 if (logger.isDebugEnabled())
3424 logger.debug("Dest path :" + destinationPath);
3426 if (destinationPath.toUpperCase().startsWith("/WEB-INF") || destinationPath.toUpperCase().startsWith("/META-INF")) {
3427 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3431 String path = getRelativePath(req);
3433 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3434 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3438 if (destinationPath.equals(path)) {
3439 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3443 // Parsing overwrite header
3444 boolean overwrite = true;
3445 String overwriteHeader = req.getHeader("Overwrite");
3447 if (overwriteHeader != null)
3448 if (overwriteHeader.equalsIgnoreCase("T"))
3453 User user = getUser(req);
3454 // Overwriting the destination
3455 boolean exists = true;
3457 getService().getResourceAtPath(user.getId(), destinationPath, true);
3458 } catch (ObjectNotFoundException e) {
3460 } catch (RpcException e) {
3461 resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
3466 // Delete destination resource, if it exists
3468 if (!deleteResource(destinationPath, req, resp, true))
3471 resp.setStatus(WebdavStatus.SC_CREATED);
3472 } else // If the destination exists, then it's a conflict
3474 resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
3477 resp.setStatus(WebdavStatus.SC_CREATED);
3479 // Copying source to destination.
3480 Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3483 result = copyResource(errorList, path, destinationPath, req);
3484 } catch (RpcException e) {
3485 resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
3488 if (!result || !errorList.isEmpty()) {
3489 sendReport(req, resp, errorList);
3492 // Removing any lock-null resource which would be present at
3493 // the destination path.
3494 lockNullResources.remove(destinationPath);
3499 * Copy a collection.
3501 * @param errorList Hashtable containing the list of errors which occurred
3502 * during the copy operation
3503 * @param source Path of the resource to be copied
3504 * @param theDest Destination path
3505 * @param req the HTTP request
3506 * @return boolean true if the copy is successful
3507 * @throws RpcException
3509 private boolean copyResource(Hashtable<String, Integer> errorList, String source, String theDest, HttpServletRequest req) throws RpcException {
3511 String dest = theDest;
3512 // Fix the destination path when copying collections.
3513 if (source.endsWith("/") && !dest.endsWith("/"))
3516 if (logger.isDebugEnabled())
3517 logger.debug("Copy: " + source + " To: " + dest);
3519 User user = getUser(req);
3520 Object object = null;
3522 object = getService().getResourceAtPath(user.getId(), source, true);
3523 } catch (ObjectNotFoundException e) {
3526 if (object instanceof FolderDTO) {
3527 FolderDTO folder = (FolderDTO) object;
3529 getService().copyFolder(user.getId(), folder.getId(), dest);
3530 } catch (ObjectNotFoundException e) {
3531 errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3533 } catch (DuplicateNameException e) {
3534 errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3536 } catch (InsufficientPermissionsException e) {
3537 errorList.put(dest, new Integer(WebdavStatus.SC_FORBIDDEN));
3542 String newSource = source;
3543 if (!source.endsWith("/"))
3545 String newDest = dest;
3546 if (!dest.endsWith("/"))
3548 // Recursively copy the subfolders.
3549 Iterator iter = folder.getSubfolders().iterator();
3550 while (iter.hasNext()) {
3551 FolderDTO subf = (FolderDTO) iter.next();
3552 String resourceName = subf.getName();
3553 copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3555 // Recursively copy the files.
3556 List<FileHeaderDTO> files;
3557 files = getService().getFiles(user.getId(), folder.getId(), true);
3558 for (FileHeaderDTO file : files) {
3559 String resourceName = file.getName();
3560 copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3562 } catch (RpcException e) {
3563 errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3565 } catch (ObjectNotFoundException e) {
3566 errorList.put(source, new Integer(WebdavStatus.SC_NOT_FOUND));
3568 } catch (InsufficientPermissionsException e) {
3569 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3573 } else if (object instanceof FileHeaderDTO) {
3574 FileHeaderDTO file = (FileHeaderDTO) object;
3576 getService().copyFile(user.getId(), file.getId(), dest);
3577 } catch (ObjectNotFoundException e) {
3578 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3580 } catch (DuplicateNameException e) {
3581 errorList.put(source, new Integer(WebdavStatus.SC_CONFLICT));
3583 } catch (InsufficientPermissionsException e) {
3584 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3586 } catch (QuotaExceededException e) {
3587 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3589 } catch (GSSIOException e) {
3590 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3594 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3601 * Delete a resource.
3603 * @param req Servlet request
3604 * @param resp Servlet response
3605 * @return boolean true if the deletion is successful
3606 * @throws IOException
3608 private boolean deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
3609 String path = getRelativePath(req);
3610 return deleteResource(path, req, resp, true);
3614 * Delete a resource.
3616 * @param path Path of the resource which is to be deleted
3617 * @param req Servlet request
3618 * @param resp Servlet response
3619 * @param setStatus Should the response status be set on successful
3621 * @return boolean true if the deletion is successful
3622 * @throws IOException
3624 private boolean deleteResource(String path, HttpServletRequest req, HttpServletResponse resp, boolean setStatus) throws IOException {
3625 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3626 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3629 String ifHeader = req.getHeader("If");
3630 if (ifHeader == null)
3633 String lockTokenHeader = req.getHeader("Lock-Token");
3634 if (lockTokenHeader == null)
3635 lockTokenHeader = "";
3637 if (isLocked(path, ifHeader + lockTokenHeader)) {
3638 resp.sendError(WebdavStatus.SC_LOCKED);
3642 User user = getUser(req);
3643 boolean exists = true;
3644 Object object = null;
3646 object = getService().getResourceAtPath(user.getId(), path, true);
3647 } catch (ObjectNotFoundException e) {
3649 } catch (RpcException e) {
3650 resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3655 resp.sendError(WebdavStatus.SC_NOT_FOUND);
3659 FolderDTO folder = null;
3660 FileHeaderDTO file = null;
3661 if (object instanceof FolderDTO)
3662 folder = (FolderDTO) object;
3664 file = (FileHeaderDTO) object;
3668 getService().deleteFile(user.getId(), file.getId());
3669 } catch (InsufficientPermissionsException e) {
3670 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
3672 } catch (ObjectNotFoundException e) {
3673 // Although we had already found the object, it was
3674 // probably deleted from another thread.
3675 resp.sendError(WebdavStatus.SC_NOT_FOUND);
3677 } catch (RpcException e) {
3678 resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3681 else if (folder != null) {
3682 Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3683 deleteCollection(req, folder, path, errorList);
3685 getService().deleteFolder(user.getId(), folder.getId());
3686 } catch (InsufficientPermissionsException e) {
3687 errorList.put(path, new Integer(WebdavStatus.SC_METHOD_NOT_ALLOWED));
3688 } catch (ObjectNotFoundException e) {
3689 errorList.put(path, new Integer(WebdavStatus.SC_NOT_FOUND));
3690 } catch (RpcException e) {
3691 errorList.put(path, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3694 if (!errorList.isEmpty()) {
3695 sendReport(req, resp, errorList);
3700 resp.setStatus(WebdavStatus.SC_NO_CONTENT);
3705 * Deletes a collection.
3707 * @param req the HTTP request
3708 * @param folder the folder whose contents will be deleted
3709 * @param path Path to the collection to be deleted
3710 * @param errorList Contains the list of the errors which occurred
3712 private void deleteCollection(HttpServletRequest req, FolderDTO folder, String path, Hashtable<String, Integer> errorList) {
3714 if (logger.isDebugEnabled())
3715 logger.debug("Delete:" + path);
3717 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3718 errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
3722 String ifHeader = req.getHeader("If");
3723 if (ifHeader == null)
3726 String lockTokenHeader = req.getHeader("Lock-Token");
3727 if (lockTokenHeader == null)
3728 lockTokenHeader = "";
3730 Iterator iter = folder.getSubfolders().iterator();
3731 while (iter.hasNext()) {
3732 FolderDTO subf = (FolderDTO) iter.next();
3733 String childName = path;
3734 if (!childName.equals("/"))
3736 childName += subf.getName();
3738 if (isLocked(childName, ifHeader + lockTokenHeader))
3739 errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
3742 User user = getUser(req);
3743 Object object = getService().getResourceAtPath(user.getId(), childName, true);
3744 FolderDTO childFolder = null;
3745 FileHeaderDTO childFile = null;
3746 if (object instanceof FolderDTO)
3747 childFolder = (FolderDTO) object;
3749 childFile = (FileHeaderDTO) object;
3750 if (childFolder != null) {
3751 deleteCollection(req, childFolder, childName, errorList);
3752 getService().deleteFolder(user.getId(), childFolder.getId());
3753 } else if (childFile != null)
3754 getService().deleteFile(user.getId(), childFile.getId());
3755 } catch (ObjectNotFoundException e) {
3756 errorList.put(childName, new Integer(WebdavStatus.SC_NOT_FOUND));
3757 } catch (InsufficientPermissionsException e) {
3758 errorList.put(childName, new Integer(WebdavStatus.SC_FORBIDDEN));
3759 } catch (RpcException e) {
3760 errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3766 * Send a multistatus element containing a complete error report to the
3769 * @param req Servlet request
3770 * @param resp Servlet response
3771 * @param errorList List of error to be displayed
3772 * @throws IOException
3774 private void sendReport(HttpServletRequest req, HttpServletResponse resp, Hashtable errorList) throws IOException {
3776 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
3778 String absoluteUri = req.getRequestURI();
3779 String relativePath = getRelativePath(req);
3781 XMLWriter generatedXML = new XMLWriter();
3782 generatedXML.writeXMLHeader();
3784 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
3786 Enumeration pathList = errorList.keys();
3787 while (pathList.hasMoreElements()) {
3789 String errorPath = (String) pathList.nextElement();
3790 int errorCode = ((Integer) errorList.get(errorPath)).intValue();
3792 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
3794 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
3795 String toAppend = errorPath.substring(relativePath.length());
3796 if (!toAppend.startsWith("/"))
3797 toAppend = "/" + toAppend;
3798 generatedXML.writeText(absoluteUri + toAppend);
3799 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
3800 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
3801 generatedXML.writeText("HTTP/1.1 " + errorCode + " " + WebdavStatus.getStatusText(errorCode));
3802 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
3804 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
3808 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
3810 Writer writer = resp.getWriter();
3811 writer.write(generatedXML.toString());
3816 // --------------------------------------------- WebdavResolver Inner Class
3818 * Work around for XML parsers that don't fully respect
3819 * {@link DocumentBuilderFactory#setExpandEntityReferences(boolean)}.
3820 * External references are filtered out for security reasons. See
3823 private class WebdavResolver implements EntityResolver {
3826 * A private copy of the servlet context.
3828 private ServletContext context;
3831 * Construct the resolver by passing the servlet context.
3833 * @param theContext the servlet context
3835 public WebdavResolver(ServletContext theContext) {
3836 context = theContext;
3839 public InputSource resolveEntity(String publicId, String systemId) {
3840 context.log("The request included a reference to an external entity with PublicID " + publicId + " and SystemID " + systemId + " which was ignored");
3841 return new InputSource(new StringReader("Ignored external entity"));
3846 * Returns the user making the request. This is the user whose credentials
3847 * were supplied in the authorization header.
3849 * @param req the HTTP request
3850 * @return the user making the request
3852 protected User getUser(HttpServletRequest req) {
3853 return (User) req.getAttribute(USER_ATTRIBUTE);
3857 * Retrieves the user who owns the requested namespace, as specified in the
3860 * @param req the HTTP request
3861 * @return the owner of the namespace
3863 protected User getOwner(HttpServletRequest req) {
3864 return (User) req.getAttribute(OWNER_ATTRIBUTE);