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 static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22 import gr.ebs.gss.client.exceptions.DuplicateNameException;
23 import gr.ebs.gss.client.exceptions.GSSIOException;
24 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
25 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
26 import gr.ebs.gss.client.exceptions.QuotaExceededException;
27 import gr.ebs.gss.client.exceptions.RpcException;
28 import gr.ebs.gss.server.domain.User;
29 import gr.ebs.gss.server.domain.dto.AuditInfoDTO;
30 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
31 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
32 import gr.ebs.gss.server.domain.dto.FolderDTO;
33 import gr.ebs.gss.server.ejb.ExternalAPI;
35 import java.io.BufferedInputStream;
36 import java.io.ByteArrayInputStream;
37 import java.io.ByteArrayOutputStream;
39 import java.io.FileInputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 import java.io.OutputStreamWriter;
44 import java.io.PrintWriter;
45 import java.io.RandomAccessFile;
46 import java.io.Reader;
47 import java.io.StringReader;
48 import java.io.StringWriter;
49 import java.io.UnsupportedEncodingException;
50 import java.io.Writer;
51 import java.net.URLDecoder;
52 import java.security.MessageDigest;
53 import java.security.NoSuchAlgorithmException;
54 import java.text.SimpleDateFormat;
55 import java.util.ArrayList;
56 import java.util.Date;
57 import java.util.Enumeration;
58 import java.util.Hashtable;
59 import java.util.Iterator;
60 import java.util.List;
61 import java.util.Locale;
62 import java.util.Stack;
63 import java.util.StringTokenizer;
64 import java.util.TimeZone;
65 import java.util.Vector;
67 import javax.naming.Context;
68 import javax.naming.InitialContext;
69 import javax.naming.NamingException;
70 import javax.rmi.PortableRemoteObject;
71 import javax.servlet.ServletContext;
72 import javax.servlet.ServletException;
73 import javax.servlet.ServletOutputStream;
74 import javax.servlet.UnavailableException;
75 import javax.servlet.http.HttpServlet;
76 import javax.servlet.http.HttpServletRequest;
77 import javax.servlet.http.HttpServletResponse;
78 import javax.xml.parsers.DocumentBuilder;
79 import javax.xml.parsers.DocumentBuilderFactory;
80 import javax.xml.parsers.ParserConfigurationException;
82 import org.apache.commons.httpclient.HttpStatus;
83 import org.apache.commons.logging.Log;
84 import org.apache.commons.logging.LogFactory;
85 import org.w3c.dom.Document;
86 import org.w3c.dom.Element;
87 import org.w3c.dom.Node;
88 import org.w3c.dom.NodeList;
89 import org.xml.sax.EntityResolver;
90 import org.xml.sax.InputSource;
91 import org.xml.sax.SAXException;
94 * The implementation of the WebDAV service.
98 public class Webdav extends HttpServlet {
101 * The request attribute containing the user who owns the requested
104 protected static final String OWNER_ATTRIBUTE = "owner";
107 * The request attribute containing the user making the request.
109 protected static final String USER_ATTRIBUTE = "user";
114 private static Log logger = LogFactory.getLog(Webdav.class);
119 protected static final String METHOD_GET = "GET";
124 protected static final String METHOD_POST = "POST";
129 protected static final String METHOD_PUT = "PUT";
134 protected static final String METHOD_DELETE = "DELETE";
139 protected static final String METHOD_HEAD = "HEAD";
144 private static final String METHOD_OPTIONS = "OPTIONS";
149 private static final String METHOD_PROPFIND = "PROPFIND";
154 private static final String METHOD_PROPPATCH = "PROPPATCH";
159 private static final String METHOD_MKCOL = "MKCOL";
164 private static final String METHOD_COPY = "COPY";
169 private static final String METHOD_MOVE = "MOVE";
174 private static final String METHOD_LOCK = "LOCK";
179 private static final String METHOD_UNLOCK = "UNLOCK";
182 * Default depth is infinite.
184 static final int INFINITY = 3; // To limit tree browsing a bit
187 * PROPFIND - Specify a property mask.
189 private static final int FIND_BY_PROPERTY = 0;
192 * PROPFIND - Display all properties.
194 private static final int FIND_ALL_PROP = 1;
197 * PROPFIND - Return property names.
199 private static final int FIND_PROPERTY_NAMES = 2;
204 private static final String DEFAULT_NAMESPACE = "DAV:";
209 private static final int LOCK_CREATION = 0;
214 private static final int LOCK_REFRESH = 1;
217 * Default lock timeout value.
219 private static final int DEFAULT_TIMEOUT = 3600;
222 * Maximum lock timeout.
224 private static final int MAX_TIMEOUT = 604800;
227 * Size of file transfer buffer in bytes.
229 private static final int BUFFER_SIZE = 4096;
232 * The output buffer size to use when serving resources.
234 protected int output = 2048;
237 * The input buffer size to use when serving resources.
239 private int input = 2048;
242 * MIME multipart separation string
244 protected static final String mimeSeparation = "GSS_MIME_BOUNDARY";
247 * Simple date format for the creation date ISO representation (partial).
249 private static final SimpleDateFormat creationDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
254 private static final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
257 * Array containing the safe characters set.
259 private static URLEncoder urlEncoder;
262 * File encoding to be used when reading static files. If none is specified
263 * the platform default is used.
265 private String fileEncoding = null;
268 * The style sheet for displaying the directory listings.
270 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;}";
273 * Secret information used to generate reasonably secure lock ids.
275 private String secret = "gss-webdav";
278 * Repository of the locks put on single resources.
283 private Hashtable<String, LockInfo> resourceLocks = new Hashtable<String, LockInfo>();
286 * Repository of the lock-null resources.
288 * Key : path of the collection containing the lock-null resource<br>
289 * Value : Vector of lock-null resource which are members of the collection.
290 * Each element of the Vector is the path associated with the lock-null
293 private Hashtable<String, Vector<String>> lockNullResources = new Hashtable<String, Vector<String>>();
296 * Vector of the heritable locks.
301 private Vector<LockInfo> collectionLocks = new Vector<LockInfo>();
306 protected static ArrayList FULL = new ArrayList();
309 * MD5 message digest provider.
311 protected static MessageDigest md5Helper;
314 * The MD5 helper object for this class.
316 protected static final MD5Encoder md5Encoder = new MD5Encoder();
319 * GMT timezone - all HTTP dates are on GMT
322 creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
323 urlEncoder = new URLEncoder();
324 urlEncoder.addSafeCharacter('-');
325 urlEncoder.addSafeCharacter('_');
326 urlEncoder.addSafeCharacter('.');
327 urlEncoder.addSafeCharacter('*');
328 urlEncoder.addSafeCharacter('/');
332 public void init() throws ServletException {
333 if (getServletConfig().getInitParameter("input") != null)
334 input = Integer.parseInt(getServletConfig().getInitParameter("input"));
336 if (getServletConfig().getInitParameter("output") != null)
337 output = Integer.parseInt(getServletConfig().getInitParameter("output"));
339 fileEncoding = getServletConfig().getInitParameter("fileEncoding");
341 // Sanity check on the specified buffer sizes
346 if (logger.isDebugEnabled())
347 logger.debug("Input buffer size=" + input + ", output buffer size=" + output);
349 if (getServletConfig().getInitParameter("secret") != null)
350 secret = getServletConfig().getInitParameter("secret");
352 // Load the MD5 helper used to calculate signatures.
354 md5Helper = MessageDigest.getInstance("MD5");
355 } catch (NoSuchAlgorithmException e) {
356 throw new UnavailableException("No MD5");
361 * A helper method that retrieves a reference to the ExternalAPI bean and
362 * stores it for future use.
364 * @return an ExternalAPI instance
365 * @throws RpcException in case an error occurs
367 protected ExternalAPI getService() throws RpcException {
369 final Context ctx = new InitialContext();
370 final Object ref = ctx.lookup(getConfiguration().getString("externalApiPath"));
371 return (ExternalAPI) PortableRemoteObject.narrow(ref, ExternalAPI.class);
372 } catch (final NamingException e) {
373 logger.error("Unable to retrieve the ExternalAPI EJB", e);
374 throw new RpcException("An error occurred while contacting the naming service");
379 public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
380 String method = request.getMethod();
382 if (logger.isDebugEnabled()) {
383 String path = request.getPathInfo();
385 path = request.getServletPath();
386 if (path == null || path.equals(""))
388 logger.debug("[" + method + "] " + path);
393 if (request.getUserPrincipal() != null) { // Let unauthenticated
394 // OPTIONS go through;
395 // all others will be
397 // authentication anyway
398 // before we get here.
399 user = getService().findUser(request.getUserPrincipal().getName());
401 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
405 request.setAttribute(USER_ATTRIBUTE, user);
406 request.setAttribute(OWNER_ATTRIBUTE, user);
407 } catch (RpcException e) {
408 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
411 if (method.equals(METHOD_GET))
412 doGet(request, response);
413 else if (method.equals(METHOD_POST))
414 doPost(request, response);
415 else if (method.equals(METHOD_PUT))
416 doPut(request, response);
417 else if (method.equals(METHOD_DELETE))
418 doDelete(request, response);
419 else if (method.equals(METHOD_HEAD))
420 doHead(request, response);
421 else if (method.equals(METHOD_PROPFIND))
422 doPropfind(request, response);
423 else if (method.equals(METHOD_PROPPATCH))
424 doProppatch(request, response);
425 else if (method.equals(METHOD_MKCOL))
426 doMkcol(request, response);
427 else if (method.equals(METHOD_COPY))
428 doCopy(request, response);
429 else if (method.equals(METHOD_MOVE))
430 doMove(request, response);
431 else if (method.equals(METHOD_LOCK))
432 doLock(request, response);
433 else if (method.equals(METHOD_UNLOCK))
434 doUnlock(request, response);
435 else if (method.equals(METHOD_OPTIONS))
436 doOptions(request, response);
438 // DefaultServlet processing for TRACE, etc.
439 super.service(request, response);
443 protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException {
444 resp.addHeader("DAV", "1,2");
445 StringBuffer methodsAllowed = new StringBuffer();
447 methodsAllowed = determineMethodsAllowed(req);
448 } catch (RpcException e) {
449 resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
452 resp.addHeader("Allow", methodsAllowed.toString());
453 resp.addHeader("MS-Author-Via", "DAV");
457 * Implement the PROPFIND method.
459 * @param req the HTTP request
460 * @param resp the HTTP response
461 * @throws ServletException
462 * @throws IOException
464 private void doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
465 String path = getRelativePath(req);
466 if (path.endsWith("/") && !path.equals("/"))
467 path = path.substring(0, path.length() - 1);
469 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
470 resp.sendError(WebdavStatus.SC_FORBIDDEN);
474 // Properties which are to be displayed.
475 Vector<String> properties = null;
477 int depth = INFINITY;
479 int type = FIND_ALL_PROP;
481 String depthStr = req.getHeader("Depth");
483 if (depthStr == null)
485 else if (depthStr.equals("0"))
487 else if (depthStr.equals("1"))
489 else if (depthStr.equals("infinity"))
492 Node propNode = null;
494 if (req.getInputStream().available() > 0) {
495 DocumentBuilder documentBuilder = getDocumentBuilder();
498 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
500 // Get the root element of the document
501 Element rootElement = document.getDocumentElement();
502 NodeList childList = rootElement.getChildNodes();
504 for (int i = 0; i < childList.getLength(); i++) {
505 Node currentNode = childList.item(i);
506 switch (currentNode.getNodeType()) {
509 case Node.ELEMENT_NODE:
510 if (currentNode.getNodeName().endsWith("prop")) {
511 type = FIND_BY_PROPERTY;
512 propNode = currentNode;
514 if (currentNode.getNodeName().endsWith("propname"))
515 type = FIND_PROPERTY_NAMES;
516 if (currentNode.getNodeName().endsWith("allprop"))
517 type = FIND_ALL_PROP;
521 } catch (SAXException e) {
522 // Something went wrong - use the defaults.
523 if (logger.isDebugEnabled())
524 logger.debug(e.getMessage());
525 } catch (IOException e) {
526 // Something went wrong - use the defaults.
527 if (logger.isDebugEnabled())
528 logger.debug(e.getMessage());
532 if (type == FIND_BY_PROPERTY) {
533 properties = new Vector<String>();
534 NodeList childList = propNode.getChildNodes();
536 for (int i = 0; i < childList.getLength(); i++) {
537 Node currentNode = childList.item(i);
538 switch (currentNode.getNodeType()) {
541 case Node.ELEMENT_NODE:
542 String nodeName = currentNode.getNodeName();
543 String propertyName = null;
544 if (nodeName.indexOf(':') != -1)
545 propertyName = nodeName.substring(nodeName.indexOf(':') + 1);
547 propertyName = nodeName;
548 // href is a live property which is handled differently
549 properties.addElement(propertyName);
554 User user = getUser(req);
555 boolean exists = true;
556 Object object = null;
558 object = getService().getResourceAtPath(user.getId(), path, true);
559 } catch (ObjectNotFoundException e) {
561 int slash = path.lastIndexOf('/');
563 String parentPath = path.substring(0, slash);
564 Vector currentLockNullResources = lockNullResources.get(parentPath);
565 if (currentLockNullResources != null) {
566 Enumeration lockNullResourcesList = currentLockNullResources.elements();
567 while (lockNullResourcesList.hasMoreElements()) {
568 String lockNullPath = (String) lockNullResourcesList.nextElement();
569 if (lockNullPath.equals(path)) {
570 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
571 resp.setContentType("text/xml; charset=UTF-8");
572 // Create multistatus object
573 XMLWriter generatedXML = new XMLWriter(resp.getWriter());
574 generatedXML.writeXMLHeader();
575 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
576 parseLockNullProperties(req, generatedXML, lockNullPath, type, properties);
577 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
578 generatedXML.sendData();
584 } catch (RpcException e) {
585 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
589 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
592 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
593 resp.setContentType("text/xml; charset=UTF-8");
594 // Create multistatus object
595 XMLWriter generatedXML = new XMLWriter(resp.getWriter());
596 generatedXML.writeXMLHeader();
597 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
599 parseProperties(req, generatedXML, path, type, properties, object);
601 // The stack always contains the object of the current level
602 Stack<String> stack = new Stack<String>();
605 // Stack of the objects one level below
606 Stack<String> stackBelow = new Stack<String>();
607 while (!stack.isEmpty() && depth >= 0) {
608 String currentPath = stack.pop();
610 object = getService().getResourceAtPath(user.getId(), currentPath, true);
611 } catch (ObjectNotFoundException e) {
613 } catch (RpcException e) {
614 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
617 parseProperties(req, generatedXML, currentPath, type, properties, object);
618 if (object instanceof FolderDTO && depth > 0) {
619 FolderDTO folder = (FolderDTO) object;
620 // Retrieve the subfolders.
621 List subfolders = folder.getSubfolders();
622 Iterator iter = subfolders.iterator();
623 while (iter.hasNext()) {
624 FolderDTO f = (FolderDTO) iter.next();
625 String newPath = currentPath;
626 if (!newPath.endsWith("/"))
628 newPath += f.getName();
629 stackBelow.push(newPath);
631 // Retrieve the files.
632 List<FileHeaderDTO> files;
634 files = getService().getFiles(user.getId(), folder.getId(), true);
635 } catch (ObjectNotFoundException e) {
636 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
638 } catch (InsufficientPermissionsException e) {
639 resp.sendError(HttpServletResponse.SC_FORBIDDEN, path);
641 } catch (RpcException e) {
642 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
645 for (FileHeaderDTO file : files) {
646 String newPath = currentPath;
647 if (!newPath.endsWith("/"))
649 newPath += file.getName();
650 stackBelow.push(newPath);
652 // Displaying the lock-null resources present in that
654 String lockPath = currentPath;
655 if (lockPath.endsWith("/"))
656 lockPath = lockPath.substring(0, lockPath.length() - 1);
657 Vector currentLockNullResources = lockNullResources.get(lockPath);
658 if (currentLockNullResources != null) {
659 Enumeration lockNullResourcesList = currentLockNullResources.elements();
660 while (lockNullResourcesList.hasMoreElements()) {
661 String lockNullPath = (String) lockNullResourcesList.nextElement();
662 parseLockNullProperties(req, generatedXML, lockNullPath, type, properties);
666 if (stack.isEmpty()) {
669 stackBelow = new Stack<String>();
671 generatedXML.sendData();
674 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
675 generatedXML.sendData();
681 * @param req the HTTP request
682 * @param resp the HTTP response
683 * @throws IOException if an error occurs while sending the response
685 private void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
687 resp.sendError(WebdavStatus.SC_LOCKED);
690 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
694 * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
697 protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
699 resp.sendError(WebdavStatus.SC_LOCKED);
702 deleteResource(req, resp);
706 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
707 // Serve the requested resource, including the data content
709 serveResource(req, resp, true);
710 } catch (ObjectNotFoundException e) {
711 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
713 } catch (InsufficientPermissionsException e) {
714 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
716 } catch (RpcException e) {
717 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
723 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
724 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
728 protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
730 resp.sendError(WebdavStatus.SC_LOCKED);
734 User user = getUser(req);
735 String path = getRelativePath(req);
736 boolean exists = true;
737 Object resource = null;
738 FileHeaderDTO file = null;
740 resource = getService().getResourceAtPath(user.getId(), path, true);
741 } catch (ObjectNotFoundException e) {
743 } catch (RpcException e) {
744 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
749 if (resource instanceof FileHeaderDTO)
750 file = (FileHeaderDTO) resource;
752 resp.sendError(HttpServletResponse.SC_CONFLICT);
755 boolean result = true;
757 // Temporary content file used to support partial PUT.
758 File contentFile = null;
760 Range range = parseContentRange(req, resp);
762 InputStream resourceInputStream = null;
764 // Append data specified in ranges to existing content for this
765 // resource - create a temporary file on the local filesystem to
766 // perform this operation.
767 // Assume just one range is specified for now
770 contentFile = executePartialPut(req, range, path);
771 } catch (RpcException e) {
772 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
774 } catch (ObjectNotFoundException e) {
775 resp.sendError(HttpServletResponse.SC_CONFLICT);
777 } catch (InsufficientPermissionsException e) {
778 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
781 resourceInputStream = new FileInputStream(contentFile);
783 resourceInputStream = req.getInputStream();
786 FolderDTO folder = null;
787 Object parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
788 if (!(parent instanceof FolderDTO)) {
789 resp.sendError(HttpServletResponse.SC_CONFLICT);
792 folder = (FolderDTO) parent;
793 String name = getLastElement(path);
794 String mimeType = getServletContext().getMimeType(name);
795 // FIXME: Add attributes
796 FileHeaderDTO fileDTO = null;
798 fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, resourceInputStream);
800 fileDTO = getService().createFile(user.getId(), folder.getId(), name, mimeType, resourceInputStream);
801 getService().updateAccounting(user, new Date(), fileDTO.getFileSize());
802 } catch (ObjectNotFoundException e) {
804 } catch (InsufficientPermissionsException e) {
805 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
807 } catch (QuotaExceededException e) {
808 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
810 } catch (GSSIOException e) {
811 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
813 } catch (RpcException e) {
814 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
816 } catch (DuplicateNameException e) {
817 resp.sendError(HttpServletResponse.SC_CONFLICT);
823 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
825 resp.setStatus(HttpServletResponse.SC_CREATED);
827 resp.sendError(HttpServletResponse.SC_CONFLICT);
829 // Removing any lock-null resource which would be present.
830 lockNullResources.remove(path);
834 protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
835 // Serve the requested resource, without the data content
837 serveResource(req, resp, false);
838 } catch (ObjectNotFoundException e) {
839 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
841 } catch (InsufficientPermissionsException e) {
842 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
844 } catch (RpcException e) {
845 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
853 * @param req the HTTP request
854 * @param resp the HTTP response
855 * @throws IOException if an error occurs while sending the response
857 private void doUnlock(HttpServletRequest req, HttpServletResponse resp) throws IOException {
859 resp.sendError(WebdavStatus.SC_LOCKED);
862 String path = getRelativePath(req);
863 String lockTokenHeader = req.getHeader("Lock-Token");
864 if (lockTokenHeader == null)
865 lockTokenHeader = "";
867 // Checking resource locks
868 LockInfo lock = resourceLocks.get(path);
869 Enumeration tokenList = null;
871 // At least one of the tokens of the locks must have been given
872 tokenList = lock.tokens.elements();
873 while (tokenList.hasMoreElements()) {
874 String token = (String) tokenList.nextElement();
875 if (lockTokenHeader.indexOf(token) != -1)
876 lock.tokens.removeElement(token);
878 if (lock.tokens.isEmpty()) {
879 resourceLocks.remove(path);
880 // Removing any lock-null resource which would be present
881 lockNullResources.remove(path);
884 // Checking inheritable collection locks
885 Enumeration collectionLocksList = collectionLocks.elements();
886 while (collectionLocksList.hasMoreElements()) {
887 lock = (LockInfo) collectionLocksList.nextElement();
888 if (path.equals(lock.path)) {
889 tokenList = lock.tokens.elements();
890 while (tokenList.hasMoreElements()) {
891 String token = (String) tokenList.nextElement();
892 if (lockTokenHeader.indexOf(token) != -1) {
893 lock.tokens.removeElement(token);
897 if (lock.tokens.isEmpty()) {
898 collectionLocks.removeElement(lock);
899 // Removing any lock-null resource which would be present
900 lockNullResources.remove(path);
904 resp.setStatus(WebdavStatus.SC_NO_CONTENT);
910 * @param req the HTTP request
911 * @param resp the HTTP response
912 * @throws IOException if an error occurs while sending the response
913 * @throws ServletException
915 private void doLock(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
917 resp.sendError(WebdavStatus.SC_LOCKED);
921 LockInfo lock = new LockInfo();
922 // Parsing lock request
924 // Parsing depth header
925 String depthStr = req.getHeader("Depth");
926 if (depthStr == null)
927 lock.depth = INFINITY;
928 else if (depthStr.equals("0"))
931 lock.depth = INFINITY;
933 // Parsing timeout header
934 int lockDuration = DEFAULT_TIMEOUT;
935 String lockDurationStr = req.getHeader("Timeout");
936 if (lockDurationStr == null)
937 lockDuration = DEFAULT_TIMEOUT;
939 int commaPos = lockDurationStr.indexOf(",");
940 // If multiple timeouts, just use the first
942 lockDurationStr = lockDurationStr.substring(0, commaPos);
943 if (lockDurationStr.startsWith("Second-"))
944 lockDuration = new Integer(lockDurationStr.substring(7)).intValue();
945 else if (lockDurationStr.equalsIgnoreCase("infinity"))
946 lockDuration = MAX_TIMEOUT;
949 lockDuration = new Integer(lockDurationStr).intValue();
950 } catch (NumberFormatException e) {
951 lockDuration = MAX_TIMEOUT;
953 if (lockDuration == 0)
954 lockDuration = DEFAULT_TIMEOUT;
955 if (lockDuration > MAX_TIMEOUT)
956 lockDuration = MAX_TIMEOUT;
958 lock.expiresAt = System.currentTimeMillis() + lockDuration * 1000;
960 int lockRequestType = LOCK_CREATION;
961 Node lockInfoNode = null;
962 DocumentBuilder documentBuilder = getDocumentBuilder();
965 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
966 // Get the root element of the document
967 Element rootElement = document.getDocumentElement();
968 lockInfoNode = rootElement;
969 } catch (IOException e) {
970 lockRequestType = LOCK_REFRESH;
971 } catch (SAXException e) {
972 lockRequestType = LOCK_REFRESH;
975 if (lockInfoNode != null) {
976 // Reading lock information
977 NodeList childList = lockInfoNode.getChildNodes();
978 StringWriter strWriter = null;
979 DOMWriter domWriter = null;
981 Node lockScopeNode = null;
982 Node lockTypeNode = null;
983 Node lockOwnerNode = null;
985 for (int i = 0; i < childList.getLength(); i++) {
986 Node currentNode = childList.item(i);
987 switch (currentNode.getNodeType()) {
990 case Node.ELEMENT_NODE:
991 String nodeName = currentNode.getNodeName();
992 if (nodeName.endsWith("lockscope"))
993 lockScopeNode = currentNode;
994 if (nodeName.endsWith("locktype"))
995 lockTypeNode = currentNode;
996 if (nodeName.endsWith("owner"))
997 lockOwnerNode = currentNode;
1002 if (lockScopeNode != null) {
1003 childList = lockScopeNode.getChildNodes();
1004 for (int i = 0; i < childList.getLength(); i++) {
1005 Node currentNode = childList.item(i);
1006 switch (currentNode.getNodeType()) {
1007 case Node.TEXT_NODE:
1009 case Node.ELEMENT_NODE:
1010 String tempScope = currentNode.getNodeName();
1011 if (tempScope.indexOf(':') != -1)
1012 lock.scope = tempScope.substring(tempScope.indexOf(':') + 1);
1014 lock.scope = tempScope;
1018 if (lock.scope == null)
1020 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1023 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1025 if (lockTypeNode != null) {
1026 childList = lockTypeNode.getChildNodes();
1027 for (int i = 0; i < childList.getLength(); i++) {
1028 Node currentNode = childList.item(i);
1029 switch (currentNode.getNodeType()) {
1030 case Node.TEXT_NODE:
1032 case Node.ELEMENT_NODE:
1033 String tempType = currentNode.getNodeName();
1034 if (tempType.indexOf(':') != -1)
1035 lock.type = tempType.substring(tempType.indexOf(':') + 1);
1037 lock.type = tempType;
1042 if (lock.type == null)
1044 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1047 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1049 if (lockOwnerNode != null) {
1050 childList = lockOwnerNode.getChildNodes();
1051 for (int i = 0; i < childList.getLength(); i++) {
1052 Node currentNode = childList.item(i);
1053 switch (currentNode.getNodeType()) {
1054 case Node.TEXT_NODE:
1055 lock.owner += currentNode.getNodeValue();
1057 case Node.ELEMENT_NODE:
1058 strWriter = new StringWriter();
1059 domWriter = new DOMWriter(strWriter, true);
1060 domWriter.setQualifiedNames(false);
1061 domWriter.print(currentNode);
1062 lock.owner += strWriter.toString();
1067 if (lock.owner == null)
1069 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1071 lock.owner = new String();
1074 String path = getRelativePath(req);
1076 User user = getUser(req);
1077 boolean exists = true;
1078 Object object = null;
1080 object = getService().getResourceAtPath(user.getId(), path, true);
1081 } catch (ObjectNotFoundException e) {
1083 } catch (RpcException e) {
1084 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1088 Enumeration locksList = null;
1089 if (lockRequestType == LOCK_CREATION) {
1090 // Generating lock id
1091 String lockTokenStr = req.getServletPath() + "-" + lock.type + "-" + lock.scope + "-" + req.getUserPrincipal() + "-" + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-" + lock.expiresAt + "-" + System.currentTimeMillis() + "-" + secret;
1092 String lockToken = md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
1094 if (exists && object instanceof FolderDTO && lock.depth == INFINITY) {
1096 // Locking a collection (and all its member resources)
1098 // Checking if a child resource of this collection is
1100 Vector<String> lockPaths = new Vector<String>();
1101 locksList = collectionLocks.elements();
1102 while (locksList.hasMoreElements()) {
1103 LockInfo currentLock = (LockInfo) locksList.nextElement();
1104 if (currentLock.hasExpired()) {
1105 resourceLocks.remove(currentLock.path);
1108 if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive()))
1109 // A child collection of this collection is locked
1110 lockPaths.addElement(currentLock.path);
1112 locksList = resourceLocks.elements();
1113 while (locksList.hasMoreElements()) {
1114 LockInfo currentLock = (LockInfo) locksList.nextElement();
1115 if (currentLock.hasExpired()) {
1116 resourceLocks.remove(currentLock.path);
1119 if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive()))
1120 // A child resource of this collection is locked
1121 lockPaths.addElement(currentLock.path);
1124 if (!lockPaths.isEmpty()) {
1125 // One of the child paths was locked
1126 // We generate a multistatus error report
1127 Enumeration lockPathsList = lockPaths.elements();
1128 resp.setStatus(WebdavStatus.SC_CONFLICT);
1129 XMLWriter generatedXML = new XMLWriter();
1130 generatedXML.writeXMLHeader();
1132 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
1134 while (lockPathsList.hasMoreElements()) {
1135 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1136 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1137 generatedXML.writeText((String) lockPathsList.nextElement());
1138 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1139 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1140 generatedXML.writeText("HTTP/1.1 " + WebdavStatus.SC_LOCKED + " " + WebdavStatus.getStatusText(WebdavStatus.SC_LOCKED));
1141 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1142 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1145 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
1147 Writer writer = resp.getWriter();
1148 writer.write(generatedXML.toString());
1153 boolean addLock = true;
1154 // Checking if there is already a shared lock on this path
1155 locksList = collectionLocks.elements();
1156 while (locksList.hasMoreElements()) {
1157 LockInfo currentLock = (LockInfo) locksList.nextElement();
1158 if (currentLock.path.equals(lock.path)) {
1159 if (currentLock.isExclusive()) {
1160 resp.sendError(WebdavStatus.SC_LOCKED);
1162 } else if (lock.isExclusive()) {
1163 resp.sendError(WebdavStatus.SC_LOCKED);
1166 currentLock.tokens.addElement(lockToken);
1172 lock.tokens.addElement(lockToken);
1173 collectionLocks.addElement(lock);
1176 // Locking a single resource
1178 // Retrieving an already existing lock on that resource
1179 LockInfo presentLock = resourceLocks.get(lock.path);
1180 if (presentLock != null) {
1181 if (presentLock.isExclusive() || lock.isExclusive()) {
1182 // If either lock is exclusive, the lock can't be
1184 resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
1187 presentLock.tokens.addElement(lockToken);
1192 lock.tokens.addElement(lockToken);
1193 resourceLocks.put(lock.path, lock);
1194 // Checking if a resource exists at this path
1197 object = getService().getResourceAtPath(user.getId(), path, true);
1198 } catch (ObjectNotFoundException e) {
1200 } catch (RpcException e) {
1201 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1205 // "Creating" a lock-null resource
1206 int slash = lock.path.lastIndexOf('/');
1207 String parentPath = lock.path.substring(0, slash);
1208 Vector<String> lockNulls = lockNullResources.get(parentPath);
1209 if (lockNulls == null) {
1210 lockNulls = new Vector<String>();
1211 lockNullResources.put(parentPath, lockNulls);
1213 lockNulls.addElement(lock.path);
1215 // Add the Lock-Token header as by RFC 2518 8.10.1
1216 // - only do this for newly created locks
1217 resp.addHeader("Lock-Token", "<opaquelocktoken:" + lockToken + ">");
1222 if (lockRequestType == LOCK_REFRESH) {
1223 String ifHeader = req.getHeader("If");
1224 if (ifHeader == null)
1226 // Checking resource locks
1227 LockInfo toRenew = resourceLocks.get(path);
1228 Enumeration tokenList = null;
1230 // At least one of the tokens of the locks must have been given
1231 tokenList = toRenew.tokens.elements();
1232 while (tokenList.hasMoreElements()) {
1233 String token = (String) tokenList.nextElement();
1234 if (ifHeader.indexOf(token) != -1) {
1235 toRenew.expiresAt = lock.expiresAt;
1240 // Checking inheritable collection locks
1241 Enumeration collectionLocksList = collectionLocks.elements();
1242 while (collectionLocksList.hasMoreElements()) {
1243 toRenew = (LockInfo) collectionLocksList.nextElement();
1244 if (path.equals(toRenew.path)) {
1245 tokenList = toRenew.tokens.elements();
1246 while (tokenList.hasMoreElements()) {
1247 String token = (String) tokenList.nextElement();
1248 if (ifHeader.indexOf(token) != -1) {
1249 toRenew.expiresAt = lock.expiresAt;
1256 // Set the status, then generate the XML response containing
1257 // the lock information.
1258 XMLWriter generatedXML = new XMLWriter();
1259 generatedXML.writeXMLHeader();
1260 generatedXML.writeElement(null, "prop" + generateNamespaceDeclarations(), XMLWriter.OPENING);
1261 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
1262 lock.toXML(generatedXML);
1263 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING);
1264 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1266 resp.setStatus(WebdavStatus.SC_OK);
1267 resp.setContentType("text/xml; charset=UTF-8");
1268 Writer writer = resp.getWriter();
1269 writer.write(generatedXML.toString());
1276 * @param req the HTTP request
1277 * @param resp the HTTP response
1278 * @throws IOException if an error occurs while sending the response
1279 * @throws ServletException
1281 private void doMove(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1282 if (isLocked(req)) {
1283 resp.sendError(WebdavStatus.SC_LOCKED);
1287 String path = getRelativePath(req);
1289 if (copyResource(req, resp))
1290 deleteResource(path, req, resp, false);
1296 * @param req the HTTP request
1297 * @param resp the HTTP response
1298 * @throws IOException if an error occurs while sending the response
1299 * @throws ServletException
1301 private void doCopy(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1302 copyResource(req, resp);
1308 * @param req the HTTP request
1309 * @param resp the HTTP response
1310 * @throws IOException if an error occurs while sending the response
1311 * @throws ServletException
1313 private void doMkcol(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1314 if (isLocked(req)) {
1315 resp.sendError(WebdavStatus.SC_LOCKED);
1318 String path = getRelativePath(req);
1319 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
1320 resp.sendError(WebdavStatus.SC_FORBIDDEN);
1324 User user = getUser(req);
1325 boolean exists = true;
1327 getService().getResourceAtPath(user.getId(), path, true);
1328 } catch (ObjectNotFoundException e) {
1330 } catch (RpcException e) {
1331 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1335 // Can't create a collection if a resource already exists at the given
1338 // Get allowed methods.
1339 StringBuffer methodsAllowed;
1341 methodsAllowed = determineMethodsAllowed(req);
1342 } catch (RpcException e) {
1343 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1346 resp.addHeader("Allow", methodsAllowed.toString());
1347 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
1351 if (req.getInputStream().available() > 0) {
1352 DocumentBuilder documentBuilder = getDocumentBuilder();
1354 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
1355 // TODO : Process this request body
1356 resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
1358 } catch (SAXException saxe) {
1359 // Parse error - assume invalid content
1360 resp.sendError(WebdavStatus.SC_BAD_REQUEST);
1367 parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
1368 } catch (ObjectNotFoundException e1) {
1369 resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1371 } catch (RpcException e1) {
1372 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1376 if (parent instanceof FolderDTO) {
1377 FolderDTO folder = (FolderDTO) parent;
1378 getService().createFolder(user.getId(), folder.getId(), getLastElement(path));
1380 resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1383 } catch (DuplicateNameException e) {
1384 // XXX If the existing name is a folder we should be returning
1385 // SC_METHOD_NOT_ALLOWED, or even better, just do the createFolder
1386 // without checking first and then deal with the exceptions.
1387 resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1389 } catch (InsufficientPermissionsException e) {
1390 resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1392 } catch (ObjectNotFoundException e) {
1393 resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1395 } catch (RpcException e) {
1396 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1399 resp.setStatus(WebdavStatus.SC_CREATED);
1400 // Removing any lock-null resource which would be present
1401 lockNullResources.remove(path);
1405 * For a provided path, remove the last element and return the rest, that is
1406 * the path of the parent folder.
1408 * @param path the specified path
1409 * @return the path of the parent folder
1410 * @throws ObjectNotFoundException if the provided string contains no path
1413 protected String getParentPath(String path) throws ObjectNotFoundException {
1414 int lastDelimiter = path.lastIndexOf('/');
1415 if (lastDelimiter == 0)
1417 if (lastDelimiter == -1)
1419 throw new ObjectNotFoundException("There is no parent in the path: " + path);
1420 else if (lastDelimiter < path.length() - 1)
1421 // Return the part before the delimiter.
1422 return path.substring(0, lastDelimiter);
1424 // Remove the trailing delimiter and then recurse.
1425 String strippedTrail = path.substring(0, lastDelimiter);
1426 return getParentPath(strippedTrail);
1431 * Get the last element in a path that denotes the file or folder name.
1433 * @param path the provided path
1434 * @return the last element in the path
1436 protected String getLastElement(String path) {
1437 int lastDelimiter = path.lastIndexOf('/');
1438 if (lastDelimiter == -1)
1441 else if (lastDelimiter < path.length() - 1)
1442 // Return the part after the delimiter.
1443 return path.substring(lastDelimiter + 1);
1445 // Remove the trailing delimiter and then recurse.
1446 String strippedTrail = path.substring(0, lastDelimiter);
1447 return getLastElement(strippedTrail);
1452 * Only use the PathInfo for determining the requested path. If the
1453 * ServletPath is non-null, it will be because the WebDAV servlet has been
1454 * mapped to a URL other than /* to configure editing at different URL than
1457 * @param request the servlet request we are processing
1458 * @return the relative path
1459 * @throws UnsupportedEncodingException
1461 protected String getRelativePath(HttpServletRequest request) {
1462 // Remove the servlet path from the request URI.
1463 String p = request.getRequestURI();
1464 String servletPath = request.getContextPath() + request.getServletPath();
1465 String result = p.substring(servletPath.length());
1467 result = URLDecoder.decode(result, "UTF-8");
1468 } catch (UnsupportedEncodingException e) {
1470 if (result == null || result.equals(""))
1477 * Return JAXP document builder instance.
1479 * @return the DocumentBuilder
1480 * @throws ServletException
1482 private DocumentBuilder getDocumentBuilder() throws ServletException {
1483 DocumentBuilder documentBuilder = null;
1484 DocumentBuilderFactory documentBuilderFactory = null;
1486 documentBuilderFactory = DocumentBuilderFactory.newInstance();
1487 documentBuilderFactory.setNamespaceAware(true);
1488 documentBuilderFactory.setExpandEntityReferences(false);
1489 documentBuilder = documentBuilderFactory.newDocumentBuilder();
1490 documentBuilder.setEntityResolver(new WebdavResolver(getServletContext()));
1491 } catch (ParserConfigurationException e) {
1492 throw new ServletException("Error while creating a document builder");
1494 return documentBuilder;
1498 * Generate the namespace declarations.
1500 * @return the namespace declarations
1502 private String generateNamespaceDeclarations() {
1503 return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
1507 * Propfind helper method. Dispays the properties of a lock-null resource.
1509 * @param req the HTTP request
1510 * @param resources Resources object associated with this context
1511 * @param generatedXML XML response to the Propfind request
1512 * @param path Path of the current resource
1513 * @param type Propfind type
1514 * @param propertiesVector If the propfind type is find properties by name,
1515 * then this Vector contains those properties
1517 private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector propertiesVector) {
1519 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1520 // (the "toUpperCase()" avoids problems on Windows systems)
1521 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF"))
1524 // Retrieving the lock associated with the lock-null resource
1525 LockInfo lock = resourceLocks.get(path);
1530 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1531 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1533 // Generating href element
1534 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1536 String absoluteUri = req.getRequestURI();
1537 String relativePath = getRelativePath(req);
1538 String toAppend = path.substring(relativePath.length());
1539 if (!toAppend.startsWith("/"))
1540 toAppend = "/" + toAppend;
1542 generatedXML.writeText(rewriteUrl(RequestUtil.normalize(absoluteUri + toAppend)));
1544 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1546 String resourceName = path;
1547 int lastSlash = path.lastIndexOf('/');
1548 if (lastSlash != -1)
1549 resourceName = resourceName.substring(lastSlash + 1);
1555 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1556 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1558 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(lock.creationDate.getTime()));
1559 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1560 generatedXML.writeData(resourceName);
1561 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1562 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(lock.creationDate.getTime(), null));
1563 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(0));
1564 generatedXML.writeProperty(null, "getcontenttype", "");
1565 generatedXML.writeProperty(null, "getetag", "");
1566 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1567 generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
1568 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1570 generatedXML.writeProperty(null, "source", "");
1572 String supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1573 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1574 generatedXML.writeText(supportedLocks);
1575 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1577 generateLockDiscovery(path, generatedXML);
1579 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1580 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1581 generatedXML.writeText(status);
1582 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1583 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1587 case FIND_PROPERTY_NAMES:
1589 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1590 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1592 generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT);
1593 generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
1594 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1595 generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT);
1596 generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT);
1597 generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT);
1598 generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT);
1599 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1600 generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
1601 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
1603 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1604 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1605 generatedXML.writeText(status);
1606 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1607 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1611 case FIND_BY_PROPERTY:
1613 Vector<String> propertiesNotFound = new Vector<String>();
1615 // Parse the list of properties
1617 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1618 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1620 Enumeration properties = propertiesVector.elements();
1622 while (properties.hasMoreElements()) {
1624 String property = (String) properties.nextElement();
1626 if (property.equals("creationdate"))
1627 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(lock.creationDate.getTime()));
1628 else if (property.equals("displayname")) {
1629 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1630 generatedXML.writeData(resourceName);
1631 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1632 } else if (property.equals("getcontentlanguage"))
1633 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1634 else if (property.equals("getcontentlength"))
1635 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(0));
1636 else if (property.equals("getcontenttype"))
1637 generatedXML.writeProperty(null, "getcontenttype", "");
1638 else if (property.equals("getetag"))
1639 generatedXML.writeProperty(null, "getetag", "");
1640 else if (property.equals("getlastmodified"))
1641 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(lock.creationDate.getTime(), null));
1642 else if (property.equals("resourcetype")) {
1643 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1644 generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
1645 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1646 } else if (property.equals("source"))
1647 generatedXML.writeProperty(null, "source", "");
1648 else if (property.equals("supportedlock")) {
1649 supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1650 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1651 generatedXML.writeText(supportedLocks);
1652 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1653 } else if (property.equals("lockdiscovery")) {
1654 if (!generateLockDiscovery(path, generatedXML))
1655 propertiesNotFound.addElement(property);
1657 propertiesNotFound.addElement(property);
1661 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1662 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1663 generatedXML.writeText(status);
1664 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1665 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1667 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1669 if (propertiesNotFoundList.hasMoreElements()) {
1671 status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
1673 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1674 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1676 while (propertiesNotFoundList.hasMoreElements())
1677 generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
1679 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1680 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1681 generatedXML.writeText(status);
1682 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1683 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1691 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1696 * Propfind helper method.
1698 * @param req The servlet request
1699 * @param resources Resources object associated with this context
1700 * @param generatedXML XML response to the Propfind request
1701 * @param path Path of the current resource
1702 * @param type Propfind type
1703 * @param propertiesVector If the propfind type is find properties by name,
1704 * then this Vector contains those properties
1705 * @param resource the resource object
1707 private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector<String> propertiesVector, Object resource) {
1709 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1710 // (the "toUpperCase()" avoids problems on Windows systems)
1711 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF"))
1714 FolderDTO folder = null;
1715 FileHeaderDTO file = null;
1716 if (resource instanceof FolderDTO)
1717 folder = (FolderDTO) resource;
1719 file = (FileHeaderDTO) resource;
1720 // Retrieve the creation date.
1723 creation = folder.getAuditInfo().getCreationDate().getTime();
1725 creation = file.getAuditInfo().getCreationDate().getTime();
1726 // Retrieve the modification date.
1727 long modification = 0;
1729 modification = folder.getAuditInfo().getCreationDate().getTime();
1731 modification = file.getAuditInfo().getCreationDate().getTime();
1733 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1734 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1736 // Generating href element
1737 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1739 String href = req.getContextPath() + req.getServletPath();
1740 if (href.endsWith("/") && path.startsWith("/"))
1741 href += path.substring(1);
1744 if (folder != null && !href.endsWith("/"))
1747 generatedXML.writeText(rewriteUrl(href));
1749 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1751 String resourceName = path;
1752 int lastSlash = path.lastIndexOf('/');
1753 if (lastSlash != -1)
1754 resourceName = resourceName.substring(lastSlash + 1);
1760 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1761 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1763 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(creation));
1764 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1765 generatedXML.writeData(resourceName);
1766 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1768 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1769 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(file.getFileSize()));
1770 String contentType = file.getMimeType();
1771 if (contentType != null)
1772 generatedXML.writeProperty(null, "getcontenttype", contentType);
1773 generatedXML.writeProperty(null, "getetag", getETag(file, null));
1774 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1776 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1777 generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
1778 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1781 generatedXML.writeProperty(null, "source", "");
1783 String supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1784 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1785 generatedXML.writeText(supportedLocks);
1786 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1788 generateLockDiscovery(path, generatedXML);
1790 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1791 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1792 generatedXML.writeText(status);
1793 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1794 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1798 case FIND_PROPERTY_NAMES:
1800 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1801 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1803 generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT);
1804 generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
1806 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1807 generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT);
1808 generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT);
1809 generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT);
1810 generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT);
1812 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1813 generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
1814 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
1816 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1817 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1818 generatedXML.writeText(status);
1819 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1820 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1824 case FIND_BY_PROPERTY:
1826 Vector<String> propertiesNotFound = new Vector<String>();
1828 // Parse the list of properties
1830 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1831 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1833 Enumeration<String> properties = propertiesVector.elements();
1835 while (properties.hasMoreElements()) {
1837 String property = properties.nextElement();
1839 if (property.equals("creationdate"))
1840 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(creation));
1841 else if (property.equals("displayname")) {
1842 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1843 generatedXML.writeData(resourceName);
1844 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1845 } else if (property.equals("getcontentlanguage")) {
1847 propertiesNotFound.addElement(property);
1849 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1850 } else if (property.equals("getcontentlength")) {
1852 propertiesNotFound.addElement(property);
1854 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(file.getFileSize()));
1855 } else if (property.equals("getcontenttype")) {
1857 propertiesNotFound.addElement(property);
1859 // XXX Once we properly store the MIME type in the
1861 // retrieve it from there.
1862 generatedXML.writeProperty(null, "getcontenttype", getServletContext().getMimeType(file.getName()));
1863 } else if (property.equals("getetag")) {
1865 propertiesNotFound.addElement(property);
1867 generatedXML.writeProperty(null, "getetag", getETag(file, null));
1868 } else if (property.equals("getlastmodified")) {
1870 propertiesNotFound.addElement(property);
1872 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1873 } else if (property.equals("resourcetype")) {
1874 if (folder != null) {
1875 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1876 generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
1877 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1879 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1880 } else if (property.equals("source"))
1881 generatedXML.writeProperty(null, "source", "");
1882 else if (property.equals("supportedlock")) {
1883 supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1884 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1885 generatedXML.writeText(supportedLocks);
1886 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1887 } else if (property.equals("lockdiscovery")) {
1888 if (!generateLockDiscovery(path, generatedXML))
1889 propertiesNotFound.addElement(property);
1891 propertiesNotFound.addElement(property);
1894 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1895 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1896 generatedXML.writeText(status);
1897 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1898 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1900 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1902 if (propertiesNotFoundList.hasMoreElements()) {
1904 status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
1906 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1907 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1909 while (propertiesNotFoundList.hasMoreElements())
1910 generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
1911 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1912 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1913 generatedXML.writeText(status);
1914 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1915 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1922 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1927 * Get the ETag associated with a file.
1929 * @param file the FileHeaderDTO object for this file
1930 * @param oldBody the old version of the file, if requested
1931 * @return a string containing the ETag
1933 protected String getETag(FileHeaderDTO file, FileBodyDTO oldBody) {
1934 if (oldBody == null)
1935 return "\"" + file.getFileSize() + "-" + file.getAuditInfo().getModificationDate().getTime() + "\"";
1936 return "\"" + oldBody.getFileSize() + "-" + oldBody.getAuditInfo().getModificationDate().getTime() + "\"";
1942 * @param path Path which has to be rewritten
1943 * @return the rewritten URL
1945 private String rewriteUrl(String path) {
1946 return urlEncoder.encode(path);
1950 * Print the lock discovery information associated with a path.
1953 * @param generatedXML XML data to which the locks info will be appended
1954 * @return true if at least one lock was displayed
1956 private boolean generateLockDiscovery(String path, XMLWriter generatedXML) {
1957 LockInfo resourceLock = resourceLocks.get(path);
1958 Enumeration collectionLocksList = collectionLocks.elements();
1959 boolean wroteStart = false;
1960 if (resourceLock != null) {
1962 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
1963 resourceLock.toXML(generatedXML);
1966 while (collectionLocksList.hasMoreElements()) {
1967 LockInfo currentLock = (LockInfo) collectionLocksList.nextElement();
1968 if (path.startsWith(currentLock.path)) {
1971 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
1973 currentLock.toXML(generatedXML);
1978 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING);
1987 * Get creation date in ISO format.
1989 * @param creationDate
1990 * @return the formatted date
1992 private String getISOCreationDate(long creationDate) {
1993 String dateValue = null;
1994 synchronized (creationDateFormat) {
1995 dateValue = creationDateFormat.format(new Date(creationDate));
1997 StringBuffer creationDateValue = new StringBuffer(dateValue);
1999 int offset = Calendar.getInstance().getTimeZone().getRawOffset()
2000 / 3600000; // FIXME ?
2002 creationDateValue.append("-");
2004 } else if (offset > 0) {
2005 creationDateValue.append("+");
2009 creationDateValue.append("0");
2010 creationDateValue.append(offset + ":00");
2012 creationDateValue.append("Z");
2015 return creationDateValue.toString();
2019 * Determines the methods normally allowed for the resource.
2021 * @param req the HTTP request
2022 * @return a list of the allowed methods
2023 * @throws RpcException if there is an error while communicating with the
2026 private StringBuffer determineMethodsAllowed(HttpServletRequest req) throws RpcException {
2027 StringBuffer methodsAllowed = new StringBuffer();
2028 boolean exists = true;
2029 Object object = null;
2030 User user = getUser(req);
2031 String path = getRelativePath(req);
2032 if (user == null && "/".equals(path))
2033 // Special case: OPTIONS request before authentication
2034 return new StringBuffer("OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND, PUT");
2036 object = getService().getResourceAtPath(user.getId(), path, true);
2037 } catch (ObjectNotFoundException e) {
2042 methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
2043 return methodsAllowed;
2046 methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
2047 methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
2048 methodsAllowed.append(", PROPFIND");
2050 if (!(object instanceof FolderDTO))
2051 methodsAllowed.append(", PUT");
2053 return methodsAllowed;
2057 * Check to see if a resource is currently write locked. The method will
2058 * look at the "If" header to make sure the client has given the appropriate
2061 * @param req the HTTP request
2062 * @return boolean true if the resource is locked (and no appropriate lock
2063 * token has been found for at least one of the non-shared locks
2064 * which are present on the resource).
2066 private boolean isLocked(HttpServletRequest req) {
2067 String path = getRelativePath(req);
2068 String ifHeader = req.getHeader("If");
2069 if (ifHeader == null)
2071 String lockTokenHeader = req.getHeader("Lock-Token");
2072 if (lockTokenHeader == null)
2073 lockTokenHeader = "";
2074 return isLocked(path, ifHeader + lockTokenHeader);
2078 * Check to see if a resource is currently write locked.
2080 * @param path Path of the resource
2081 * @param ifHeader "If" HTTP header which was included in the request
2082 * @return boolean true if the resource is locked (and no appropriate lock
2083 * token has been found for at least one of the non-shared locks
2084 * which are present on the resource).
2086 private boolean isLocked(String path, String ifHeader) {
2087 // Checking resource locks
2088 LockInfo lock = resourceLocks.get(path);
2089 Enumeration tokenList = null;
2090 if (lock != null && lock.hasExpired())
2091 resourceLocks.remove(path);
2092 else if (lock != null) {
2093 // At least one of the tokens of the locks must have been given
2094 tokenList = lock.tokens.elements();
2095 boolean tokenMatch = false;
2096 while (tokenList.hasMoreElements()) {
2097 String token = (String) tokenList.nextElement();
2098 if (ifHeader.indexOf(token) != -1)
2104 // Checking inheritable collection locks
2105 Enumeration collectionLocksList = collectionLocks.elements();
2106 while (collectionLocksList.hasMoreElements()) {
2107 lock = (LockInfo) collectionLocksList.nextElement();
2108 if (lock.hasExpired())
2109 collectionLocks.removeElement(lock);
2110 else if (path.startsWith(lock.path)) {
2111 tokenList = lock.tokens.elements();
2112 boolean tokenMatch = false;
2113 while (tokenList.hasMoreElements()) {
2114 String token = (String) tokenList.nextElement();
2115 if (ifHeader.indexOf(token) != -1)
2126 * Parse the content-range header.
2128 * @param request The servlet request we are processing
2129 * @param response The servlet response we are creating
2131 * @throws IOException
2133 protected Range parseContentRange(HttpServletRequest request, HttpServletResponse response) throws IOException {
2135 // Retrieving the content-range header (if any is specified
2136 String rangeHeader = request.getHeader("Content-Range");
2138 if (rangeHeader == null)
2141 // bytes is the only range unit supported
2142 if (!rangeHeader.startsWith("bytes")) {
2143 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2147 rangeHeader = rangeHeader.substring(6).trim();
2149 int dashPos = rangeHeader.indexOf('-');
2150 int slashPos = rangeHeader.indexOf('/');
2152 if (dashPos == -1) {
2153 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2157 if (slashPos == -1) {
2158 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2162 Range range = new Range();
2165 range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
2166 range.end = Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
2167 range.length = Long.parseLong(rangeHeader.substring(slashPos + 1, rangeHeader.length()));
2168 } catch (NumberFormatException e) {
2169 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2173 if (!range.validate()) {
2174 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2183 * Handle a partial PUT. New content specified in request is appended to
2184 * existing content in oldRevisionContent (if present). This code does not
2185 * support simultaneous partial updates to the same resource.
2191 * @throws IOException
2192 * @throws RpcException
2193 * @throws InsufficientPermissionsException
2194 * @throws ObjectNotFoundException
2196 protected File executePartialPut(HttpServletRequest req, Range range, String path) throws IOException, RpcException, ObjectNotFoundException, InsufficientPermissionsException {
2197 // Append data specified in ranges to existing content for this
2198 // resource - create a temporary file on the local file system to
2199 // perform this operation.
2200 File tempDir = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
2201 // Convert all '/' characters to '.' in resourcePath
2202 String convertedResourcePath = path.replace('/', '.');
2203 File contentFile = new File(tempDir, convertedResourcePath);
2204 if (contentFile.createNewFile())
2205 // Clean up contentFile when Tomcat is terminated.
2206 contentFile.deleteOnExit();
2208 RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw");
2210 User user = getUser(req);
2211 User owner = getOwner(req);
2212 FileHeaderDTO oldResource = null;
2214 Object obj = getService().getResourceAtPath(owner.getId(), path, true);
2215 if (obj instanceof FileHeaderDTO)
2216 oldResource = (FileHeaderDTO) obj;
2217 } catch (ObjectNotFoundException e) {
2221 // Copy data in oldRevisionContent to contentFile
2222 if (oldResource != null) {
2223 InputStream contents = getService().getFileContents(user.getId(), oldResource.getId());
2224 BufferedInputStream bufOldRevStream = new BufferedInputStream(contents, BUFFER_SIZE);
2227 byte[] copyBuffer = new byte[BUFFER_SIZE];
2228 while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1)
2229 randAccessContentFile.write(copyBuffer, 0, numBytesRead);
2231 bufOldRevStream.close();
2234 randAccessContentFile.setLength(range.length);
2236 // Append data in request input stream to contentFile
2237 randAccessContentFile.seek(range.start);
2239 byte[] transferBuffer = new byte[BUFFER_SIZE];
2240 BufferedInputStream requestBufInStream = new BufferedInputStream(req.getInputStream(), BUFFER_SIZE);
2241 while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1)
2242 randAccessContentFile.write(transferBuffer, 0, numBytesRead);
2243 randAccessContentFile.close();
2244 requestBufInStream.close();
2251 * Serve the specified resource, optionally including the data content.
2253 * @param req The servlet request we are processing
2254 * @param resp The servlet response we are creating
2255 * @param content Should the content be included?
2256 * @exception IOException if an input/output error occurs
2257 * @exception ServletException if a servlet-specified error occurs
2258 * @throws RpcException
2259 * @throws InsufficientPermissionsException
2260 * @throws ObjectNotFoundException
2262 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException, ServletException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2264 // Identify the requested resource path
2265 String path = getRelativePath(req);
2266 if (logger.isDebugEnabled())
2268 logger.debug("Serving resource '" + path + "' headers and data");
2270 logger.debug("Serving resource '" + path + "' headers only");
2272 User user = getUser(req);
2273 boolean exists = true;
2274 Object resource = null;
2275 FileHeaderDTO file = null;
2276 FolderDTO folder = null;
2278 resource = getService().getResourceAtPath(user.getId(), path, true);
2279 } catch (ObjectNotFoundException e) {
2281 } catch (RpcException e) {
2282 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
2287 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
2291 if (resource instanceof FolderDTO)
2292 folder = (FolderDTO) resource;
2294 file = (FileHeaderDTO) resource;
2296 // If the resource is not a collection, and the resource path
2297 // ends with "/" or "\", return NOT FOUND
2299 if (path.endsWith("/") || path.endsWith("\\")) {
2300 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
2304 // Check if the conditions specified in the optional If headers are
2307 // Checking If headers
2308 if (!checkIfHeaders(req, resp, file, null))
2311 // Find content type.
2312 String contentType = null;
2314 contentType = file.getMimeType();
2315 if (contentType == null) {
2316 contentType = getServletContext().getMimeType(file.getName());
2317 file.setMimeType(contentType);
2320 contentType = "text/html;charset=UTF-8";
2322 ArrayList ranges = null;
2323 long contentLength = -1L;
2326 // Accept ranges header
2327 resp.setHeader("Accept-Ranges", "bytes");
2328 // Parse range specifier
2329 ranges = parseRange(req, resp, file, null);
2331 resp.setHeader("ETag", getETag(file, null));
2332 // Last-Modified header
2333 resp.setHeader("Last-Modified", getLastModifiedHttp(file.getAuditInfo()));
2334 // Get content length
2335 contentLength = file.getFileSize();
2336 // Special case for zero length files, which would cause a
2337 // (silent) ISE when setting the output buffer size
2338 if (contentLength == 0L)
2342 ServletOutputStream ostream = null;
2343 PrintWriter writer = null;
2347 ostream = resp.getOutputStream();
2348 } catch (IllegalStateException e) {
2349 // If it fails, we try to get a Writer instead if we're
2350 // trying to serve a text file
2351 if (contentType == null || contentType.startsWith("text") || contentType.endsWith("xml"))
2352 writer = resp.getWriter();
2357 if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
2358 // Set the appropriate output headers
2359 if (contentType != null) {
2360 if (logger.isDebugEnabled())
2361 logger.debug("DefaultServlet.serveFile: contentType='" + contentType + "'");
2362 resp.setContentType(contentType);
2364 if (file != null && contentLength >= 0) {
2365 if (logger.isDebugEnabled())
2366 logger.debug("DefaultServlet.serveFile: contentLength=" + contentLength);
2367 if (contentLength < Integer.MAX_VALUE)
2368 resp.setContentLength((int) contentLength);
2370 // Set the content-length as String to be able to use a long
2371 resp.setHeader("content-length", "" + contentLength);
2374 InputStream renderResult = null;
2377 // Serve the directory browser
2378 renderResult = renderHtml(req.getContextPath(), path, folder, req);
2380 // Copy the input stream to our output stream (if requested)
2383 resp.setBufferSize(output);
2384 } catch (IllegalStateException e) {
2387 if (ostream != null)
2388 copy(file, renderResult, ostream, req, null);
2390 copy(file, renderResult, writer, req, null);
2391 getService().updateAccounting(user, new Date(), contentLength);
2394 if (ranges == null || ranges.isEmpty())
2396 // Partial content response.
2397 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
2399 if (ranges.size() == 1) {
2400 Range range = (Range) ranges.get(0);
2401 resp.addHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + range.length);
2402 long length = range.end - range.start + 1;
2403 if (length < Integer.MAX_VALUE)
2404 resp.setContentLength((int) length);
2406 // Set the content-length as String to be able to use a long
2407 resp.setHeader("content-length", "" + length);
2409 if (contentType != null) {
2410 if (logger.isDebugEnabled())
2411 logger.debug("DefaultServlet.serveFile: contentType='" + contentType + "'");
2412 resp.setContentType(contentType);
2417 resp.setBufferSize(output);
2418 } catch (IllegalStateException e) {
2421 if (ostream != null)
2422 copy(file, ostream, range, req, null);
2424 copy(file, writer, range, req, null);
2425 getService().updateAccounting(user, new Date(), contentLength);
2430 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
2434 resp.setBufferSize(output);
2435 } catch (IllegalStateException e) {
2438 if (ostream != null)
2439 copy(file, ostream, ranges.iterator(), contentType, req, null);
2441 copy(file, writer, ranges.iterator(), contentType, req, null);
2451 * Retrieve the last modified date of a resource in HTTP format.
2453 * @param auditInfo the audit info for the specified resource
2454 * @return the last modified date in HTTP format
2456 protected String getLastModifiedHttp(AuditInfoDTO auditInfo) {
2457 Date modifiedDate = auditInfo.getModificationDate();
2458 if (modifiedDate == null)
2459 modifiedDate = auditInfo.getCreationDate();
2460 if (modifiedDate == null)
2461 modifiedDate = new Date();
2462 String lastModifiedHttp = null;
2463 synchronized (format) {
2464 lastModifiedHttp = format.format(modifiedDate);
2466 return lastModifiedHttp;
2470 * Parse the range header.
2472 * @param request The servlet request we are processing
2473 * @param response The servlet response we are creating
2475 * @param oldBody the old version of the file, if requested
2476 * @return Vector of ranges
2477 * @throws IOException
2479 protected ArrayList parseRange(HttpServletRequest request, HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2480 // Checking If-Range
2481 String headerValue = request.getHeader("If-Range");
2482 if (headerValue != null) {
2483 long headerValueTime = -1L;
2485 headerValueTime = request.getDateHeader("If-Range");
2486 } catch (IllegalArgumentException e) {
2490 String eTag = getETag(file, oldBody);
2491 long lastModified = oldBody == null ?
2492 file.getAuditInfo().getModificationDate().getTime() :
2493 oldBody.getAuditInfo().getModificationDate().getTime();
2495 if (headerValueTime == -1L) {
2496 // If the ETag the client gave does not match the entity
2497 // etag, then the entire entity is returned.
2498 if (!eTag.equals(headerValue.trim()))
2501 // If the timestamp of the entity the client got is older than
2502 // the last modification date of the entity, the entire entity
2504 if (lastModified > headerValueTime + 1000)
2508 long fileLength = oldBody == null ? file.getFileSize() : oldBody.getFileSize();
2509 if (fileLength == 0)
2512 // Retrieving the range header (if any is specified).
2513 String rangeHeader = request.getHeader("Range");
2515 if (rangeHeader == null)
2517 // bytes is the only range unit supported (and I don't see the point
2518 // of adding new ones).
2519 if (!rangeHeader.startsWith("bytes")) {
2520 response.addHeader("Content-Range", "bytes */" + fileLength);
2521 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2525 rangeHeader = rangeHeader.substring(6);
2527 // Vector that will contain all the ranges which are successfully
2529 ArrayList<Range> result = new ArrayList<Range>();
2530 StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
2531 // Parsing the range list
2532 while (commaTokenizer.hasMoreTokens()) {
2533 String rangeDefinition = commaTokenizer.nextToken().trim();
2535 Range currentRange = new Range();
2536 currentRange.length = fileLength;
2538 int dashPos = rangeDefinition.indexOf('-');
2540 if (dashPos == -1) {
2541 response.addHeader("Content-Range", "bytes */" + fileLength);
2542 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2548 long offset = Long.parseLong(rangeDefinition);
2549 currentRange.start = fileLength + offset;
2550 currentRange.end = fileLength - 1;
2551 } catch (NumberFormatException e) {
2552 response.addHeader("Content-Range", "bytes */" + fileLength);
2553 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2558 currentRange.start = Long.parseLong(rangeDefinition.substring(0, dashPos));
2559 if (dashPos < rangeDefinition.length() - 1)
2560 currentRange.end = Long.parseLong(rangeDefinition.substring(dashPos + 1, rangeDefinition.length()));
2562 currentRange.end = fileLength - 1;
2563 } catch (NumberFormatException e) {
2564 response.addHeader("Content-Range", "bytes */" + fileLength);
2565 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2569 if (!currentRange.validate()) {
2570 response.addHeader("Content-Range", "bytes */" + fileLength);
2571 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2574 result.add(currentRange);
2580 * Check if the conditions specified in the optional If headers are
2583 * @param request The servlet request we are processing
2584 * @param response The servlet response we are creating
2585 * @param file the file resource against which the checks will be made
2586 * @param oldBody the old version of the file, if requested
2587 * @return boolean true if the resource meets all the specified conditions,
2588 * and false if any of the conditions is not satisfied, in which
2589 * case request processing is stopped
2590 * @throws IOException
2592 protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response,
2593 FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2594 // TODO : Checking the WebDAV If header
2595 return checkIfMatch(request, response, file, oldBody) &&
2596 checkIfModifiedSince(request, response, file, oldBody) &&
2597 checkIfNoneMatch(request, response, file, oldBody) &&
2598 checkIfUnmodifiedSince(request, response, file, oldBody);
2602 * Check if the if-match condition is satisfied.
2604 * @param request The servlet request we are processing
2605 * @param response The servlet response we are creating
2606 * @param file the file object
2607 * @param oldBody the old version of the file, if requested
2608 * @return boolean true if the resource meets the specified condition, and
2609 * false if the condition is not satisfied, in which case request
2610 * processing is stopped
2611 * @throws IOException
2613 private boolean checkIfMatch(HttpServletRequest request, HttpServletResponse response,
2614 FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2615 String eTag = getETag(file, oldBody);
2616 String headerValue = request.getHeader("If-Match");
2617 if (headerValue != null)
2618 if (headerValue.indexOf('*') == -1) {
2619 StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2620 boolean conditionSatisfied = false;
2621 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2622 String currentToken = commaTokenizer.nextToken();
2623 if (currentToken.trim().equals(eTag))
2624 conditionSatisfied = true;
2626 // If none of the given ETags match, 412 Precodition failed is
2628 if (!conditionSatisfied) {
2629 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2637 * Check if the if-modified-since condition is satisfied.
2639 * @param request The servlet request we are processing
2640 * @param response The servlet response we are creating
2641 * @param file the file object
2642 * @param oldBody the old version of the file, if requested
2643 * @return boolean true if the resource meets the specified condition, and
2644 * false if the condition is not satisfied, in which case request
2645 * processing is stopped
2647 private boolean checkIfModifiedSince(HttpServletRequest request,
2648 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody) {
2650 long headerValue = request.getDateHeader("If-Modified-Since");
2651 long lastModified = oldBody == null ?
2652 file.getAuditInfo().getModificationDate().getTime() :
2653 oldBody.getAuditInfo().getModificationDate().getTime();
2654 if (headerValue != -1)
2655 // If an If-None-Match header has been specified, if modified
2656 // since is ignored.
2657 if (request.getHeader("If-None-Match") == null && lastModified < headerValue + 1000) {
2658 // The entity has not been modified since the date
2659 // specified by the client. This is not an error case.
2660 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2661 response.setHeader("ETag", getETag(file, oldBody));
2664 } catch (IllegalArgumentException illegalArgument) {
2671 * Check if the if-none-match condition is satisfied.
2673 * @param request The servlet request we are processing
2674 * @param response The servlet response we are creating
2675 * @param file the file object
2676 * @param oldBody the old version of the file, if requested
2677 * @return boolean true if the resource meets the specified condition, and
2678 * false if the condition is not satisfied, in which case request
2679 * processing is stopped
2680 * @throws IOException
2682 private boolean checkIfNoneMatch(HttpServletRequest request,
2683 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody)
2684 throws IOException {
2685 String eTag = getETag(file, oldBody);
2686 String headerValue = request.getHeader("If-None-Match");
2687 if (headerValue != null) {
2688 boolean conditionSatisfied = false;
2689 if (!headerValue.equals("*")) {
2690 StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2691 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2692 String currentToken = commaTokenizer.nextToken();
2693 if (currentToken.trim().equals(eTag))
2694 conditionSatisfied = true;
2697 conditionSatisfied = true;
2698 if (conditionSatisfied) {
2699 // For GET and HEAD, we should respond with 304 Not Modified.
2700 // For every other method, 412 Precondition Failed is sent
2702 if ("GET".equals(request.getMethod()) || "HEAD".equals(request.getMethod())) {
2703 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2704 response.setHeader("ETag", getETag(file, oldBody));
2707 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2715 * Check if the if-unmodified-since condition is satisfied.
2717 * @param request The servlet request we are processing
2718 * @param response The servlet response we are creating
2719 * @param file the file object
2720 * @param oldBody the old version of the file, if requested
2721 * @return boolean true if the resource meets the specified condition, and
2722 * false if the condition is not satisfied, in which case request
2723 * processing is stopped
2724 * @throws IOException
2726 private boolean checkIfUnmodifiedSince(HttpServletRequest request,
2727 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody)
2728 throws IOException {
2730 long lastModified = oldBody == null ?
2731 file.getAuditInfo().getModificationDate().getTime() :
2732 oldBody.getAuditInfo().getModificationDate().getTime();
2733 long headerValue = request.getDateHeader("If-Unmodified-Since");
2734 if (headerValue != -1)
2735 if (lastModified >= headerValue + 1000) {
2736 // The entity has not been modified since the date
2737 // specified by the client. This is not an error case.
2738 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2741 } catch (IllegalArgumentException illegalArgument) {
2748 * Copy the contents of the specified input stream to the specified output
2749 * stream, and ensure that both streams are closed before returning (even in
2750 * the face of an exception).
2752 * @param file the file resource
2754 * @param ostream The output stream to write to
2755 * @param req the HTTP request
2756 * @param oldBody the old version of the file, if requested
2757 * @exception IOException if an input/output error occurs
2758 * @throws RpcException
2759 * @throws InsufficientPermissionsException
2760 * @throws ObjectNotFoundException
2762 protected void copy(FileHeaderDTO file, InputStream is, ServletOutputStream ostream,
2763 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
2764 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2765 IOException exception = null;
2766 InputStream resourceInputStream = null;
2767 User user = getUser(req);
2768 // Files open for all will not have specified a calling user in the request.
2770 user = getOwner(req);
2772 throw new ObjectNotFoundException("No user or owner specified");
2774 resourceInputStream = oldBody == null ?
2775 getService().getFileContents(user.getId(), file.getId()) :
2776 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2778 resourceInputStream = is;
2780 InputStream istream = new BufferedInputStream(resourceInputStream, input);
2781 // Copy the input stream to the output stream
2782 exception = copyRange(istream, ostream);
2783 // Clean up the input stream
2785 // Rethrow any exception that has occurred
2786 if (exception != null)
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 istream The input stream to read from
2796 * @param ostream The output stream to write to
2797 * @return Exception which occurred during processing
2799 private IOException copyRange(InputStream istream, ServletOutputStream ostream) {
2800 // Copy the input stream to the output stream
2801 IOException exception = null;
2802 byte buffer[] = new byte[input];
2803 int len = buffer.length;
2806 len = istream.read(buffer);
2809 ostream.write(buffer, 0, len);
2810 } catch (IOException e) {
2819 * Copy the contents of the specified input stream to the specified output
2820 * stream, and ensure that both streams are closed before returning (even in
2821 * the face of an exception).
2825 * @param resourceInfo The resource info
2826 * @param writer The writer to write to
2827 * @param req the HTTP request
2828 * @param oldBody the old version of the file, if requested
2829 * @exception IOException if an input/output error occurs
2830 * @throws RpcException
2831 * @throws InsufficientPermissionsException
2832 * @throws ObjectNotFoundException
2834 protected void copy(FileHeaderDTO file, InputStream is, PrintWriter writer,
2835 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
2836 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2837 IOException exception = null;
2839 User user = getUser(req);
2840 InputStream resourceInputStream = null;
2842 resourceInputStream = oldBody == null ?
2843 getService().getFileContents(user.getId(), file.getId()) :
2844 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2846 resourceInputStream = is;
2849 if (fileEncoding == null)
2850 reader = new InputStreamReader(resourceInputStream);
2852 reader = new InputStreamReader(resourceInputStream, fileEncoding);
2854 // Copy the input stream to the output stream
2855 exception = copyRange(reader, writer);
2856 // Clean up the reader
2858 // Rethrow any exception that has occurred
2859 if (exception != null)
2864 * Copy the contents of the specified input stream to the specified output
2865 * stream, and ensure that both streams are closed before returning (even in
2866 * the face of an exception).
2868 * @param reader The reader to read from
2869 * @param writer The writer to write to
2870 * @return Exception which occurred during processing
2872 private IOException copyRange(Reader reader, PrintWriter writer) {
2873 // Copy the input stream to the output stream
2874 IOException exception = null;
2875 char buffer[] = new char[input];
2876 int len = buffer.length;
2879 len = reader.read(buffer);
2882 writer.write(buffer, 0, len);
2883 } catch (IOException e) {
2892 * Copy the contents of the specified input stream to the specified output
2893 * stream, and ensure that both streams are closed before returning (even in
2894 * the face of an exception).
2897 * @param writer The writer to write to
2898 * @param ranges Enumeration of the ranges the client wanted to retrieve
2899 * @param contentType Content type of the resource
2900 * @param req the HTTP request
2901 * @param oldBody the old version of the file, if requested
2902 * @exception IOException if an input/output error occurs
2903 * @throws RpcException
2904 * @throws InsufficientPermissionsException
2905 * @throws ObjectNotFoundException
2907 protected void copy(FileHeaderDTO file, PrintWriter writer, Iterator ranges,
2908 String contentType, HttpServletRequest req, FileBodyDTO oldBody)
2909 throws IOException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2910 User user = getUser(req);
2911 IOException exception = null;
2912 while (exception == null && ranges.hasNext()) {
2913 InputStream resourceInputStream = oldBody == null ?
2914 getService().getFileContents(user.getId(), file.getId()) :
2915 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2917 if (fileEncoding == null)
2918 reader = new InputStreamReader(resourceInputStream);
2920 reader = new InputStreamReader(resourceInputStream, fileEncoding);
2921 Range currentRange = (Range) ranges.next();
2922 // Writing MIME header.
2924 writer.println("--" + mimeSeparation);
2925 if (contentType != null)
2926 writer.println("Content-Type: " + contentType);
2927 writer.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
2930 exception = copyRange(reader, writer, currentRange.start, currentRange.end);
2934 writer.print("--" + mimeSeparation + "--");
2935 // Rethrow any exception that has occurred
2936 if (exception != null)
2941 * Copy the contents of the specified input stream to the specified output
2942 * stream, and ensure that both streams are closed before returning (even in
2943 * the face of an exception).
2945 * @param istream The input stream to read from
2946 * @param ostream The output stream to write to
2947 * @param start Start of the range which will be copied
2948 * @param end End of the range which will be copied
2949 * @return Exception which occurred during processing
2951 private IOException copyRange(InputStream istream, ServletOutputStream ostream, long start, long end) {
2952 if (logger.isDebugEnabled())
2953 logger.debug("Serving bytes:" + start + "-" + end);
2955 istream.skip(start);
2956 } catch (IOException e) {
2959 IOException exception = null;
2960 long bytesToRead = end - start + 1;
2961 byte buffer[] = new byte[input];
2962 int len = buffer.length;
2963 while (bytesToRead > 0 && len >= buffer.length) {
2965 len = istream.read(buffer);
2966 if (bytesToRead >= len) {
2967 ostream.write(buffer, 0, len);
2970 ostream.write(buffer, 0, (int) bytesToRead);
2973 } catch (IOException e) {
2977 if (len < buffer.length)
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 reader The reader to read from
2989 * @param writer The writer 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(Reader reader, PrintWriter writer, long start, long end) {
2997 } catch (IOException e) {
3000 IOException exception = null;
3001 long bytesToRead = end - start + 1;
3002 char buffer[] = new char[input];
3003 int len = buffer.length;
3004 while (bytesToRead > 0 && len >= buffer.length) {
3006 len = reader.read(buffer);
3007 if (bytesToRead >= len) {
3008 writer.write(buffer, 0, len);
3011 writer.write(buffer, 0, (int) bytesToRead);
3014 } catch (IOException e) {
3018 if (len < buffer.length)
3025 * Copy the contents of the specified input stream to the specified output
3026 * stream, and ensure that both streams are closed before returning (even in
3027 * the face of an exception).
3030 * @param ostream The output stream to write to
3031 * @param range Range the client wanted to retrieve
3032 * @param req the HTTP request
3033 * @param oldBody the old version of the file, if requested
3034 * @exception IOException if an input/output error occurs
3035 * @throws RpcException
3036 * @throws InsufficientPermissionsException
3037 * @throws ObjectNotFoundException
3039 protected void copy(FileHeaderDTO file, ServletOutputStream ostream, Range range,
3040 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
3041 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
3042 IOException exception = null;
3043 User user = getUser(req);
3044 InputStream resourceInputStream = oldBody == null ?
3045 getService().getFileContents(user.getId(), file.getId()) :
3046 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3047 InputStream istream = new BufferedInputStream(resourceInputStream, input);
3048 exception = copyRange(istream, ostream, range.start, range.end);
3049 // Clean up the input stream
3051 // Rethrow any exception that has occurred
3052 if (exception != null)
3057 * Copy the contents of the specified input stream to the specified output
3058 * stream, and ensure that both streams are closed before returning (even in
3059 * the face of an exception).
3062 * @param writer The writer to write to
3063 * @param range Range the client wanted to retrieve
3064 * @param req the HTTP request
3065 * @param oldBody the old version of the file, if requested
3066 * @exception IOException if an input/output error occurs
3067 * @throws RpcException
3068 * @throws InsufficientPermissionsException
3069 * @throws ObjectNotFoundException
3071 protected void copy(FileHeaderDTO file, PrintWriter writer, Range range,
3072 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
3073 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
3074 IOException exception = null;
3075 User user = getUser(req);
3076 InputStream resourceInputStream = oldBody == null ?
3077 getService().getFileContents(user.getId(), file.getId()) :
3078 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3080 if (fileEncoding == null)
3081 reader = new InputStreamReader(resourceInputStream);
3083 reader = new InputStreamReader(resourceInputStream, fileEncoding);
3085 exception = copyRange(reader, writer, range.start, range.end);
3086 // Clean up the input stream
3088 // Rethrow any exception that has occurred
3089 if (exception != null)
3094 * Copy the contents of the specified input stream to the specified output
3095 * stream, and ensure that both streams are closed before returning (even in
3096 * the face of an exception).
3099 * @param ostream The output stream to write to
3100 * @param ranges Enumeration of the ranges the client wanted to retrieve
3101 * @param contentType Content type of the resource
3102 * @param req the HTTP request
3103 * @param oldBody the old version of the file, if requested
3104 * @exception IOException if an input/output error occurs
3105 * @throws RpcException
3106 * @throws InsufficientPermissionsException
3107 * @throws ObjectNotFoundException
3109 protected void copy(FileHeaderDTO file, ServletOutputStream ostream,
3110 Iterator ranges, String contentType, HttpServletRequest req,
3111 FileBodyDTO oldBody) throws IOException, ObjectNotFoundException,
3112 InsufficientPermissionsException, RpcException {
3113 IOException exception = null;
3114 User user = getUser(req);
3115 while (exception == null && ranges.hasNext()) {
3116 InputStream resourceInputStream = oldBody == null ?
3117 getService().getFileContents(user.getId(), file.getId()) :
3118 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3119 InputStream istream = new BufferedInputStream(resourceInputStream, input);
3120 Range currentRange = (Range) ranges.next();
3121 // Writing MIME header.
3123 ostream.println("--" + mimeSeparation);
3124 if (contentType != null)
3125 ostream.println("Content-Type: " + contentType);
3126 ostream.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
3130 exception = copyRange(istream, ostream, currentRange.start, currentRange.end);
3135 ostream.print("--" + mimeSeparation + "--");
3136 // Rethrow any exception that has occurred
3137 if (exception != null)
3142 * Return an InputStream to an HTML representation of the contents of this
3145 * @param contextPath Context path to which our internal paths are relative
3146 * @param path the requested path to the resource
3147 * @param folder the specified directory
3148 * @param req the HTTP request
3149 * @return an input stream with the rendered contents
3150 * @throws IOException
3151 * @throws ServletException
3153 private InputStream renderHtml(String contextPath, String path, FolderDTO folder, HttpServletRequest req) throws IOException, ServletException {
3154 String name = folder.getName();
3155 // Prepare a writer to a buffered area
3156 ByteArrayOutputStream stream = new ByteArrayOutputStream();
3157 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
3158 PrintWriter writer = new PrintWriter(osWriter);
3159 StringBuffer sb = new StringBuffer();
3160 // rewriteUrl(contextPath) is expensive. cache result for later reuse
3161 String rewrittenContextPath = rewriteUrl(contextPath);
3162 // Render the page header
3163 sb.append("<html>\r\n");
3164 sb.append("<head>\r\n");
3165 sb.append("<title>");
3166 sb.append("Index of " + name);
3167 sb.append("</title>\r\n");
3168 sb.append("<STYLE><!--");
3170 sb.append("--></STYLE> ");
3171 sb.append("</head>\r\n");
3172 sb.append("<body>");
3174 sb.append("Index of " + name);
3176 // Render the link to our parent (if required)
3177 String parentDirectory = path;
3178 if (parentDirectory.endsWith("/"))
3179 parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
3180 int slash = parentDirectory.lastIndexOf('/');
3182 String parent = path.substring(0, slash);
3183 sb.append(" - <a href=\"");
3184 sb.append(rewrittenContextPath);
3185 if (parent.equals(""))
3187 sb.append(rewriteUrl(parent));
3188 if (!parent.endsWith("/"))
3192 sb.append("Up To " + parent);
3198 sb.append("<HR size=\"1\" noshade=\"noshade\">");
3200 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
3202 // Render the column headings
3203 sb.append("<tr>\r\n");
3204 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
3206 sb.append("</strong></font></td>\r\n");
3207 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
3209 sb.append("</strong></font></td>\r\n");
3210 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
3211 sb.append("Last modified");
3212 sb.append("</strong></font></td>\r\n");
3214 // Render the directory entries within this directory
3215 boolean shade = false;
3216 Iterator iter = folder.getSubfolders().iterator();
3217 while (iter.hasNext()) {
3218 FolderDTO subf = (FolderDTO) iter.next();
3219 String resourceName = subf.getName();
3220 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
3225 sb.append(" bgcolor=\"#eeeeee\"");
3229 sb.append("<td align=\"left\"> \r\n");
3230 sb.append("<a href=\"");
3231 sb.append(rewrittenContextPath);
3232 sb.append(rewriteUrl(path + resourceName));
3234 sb.append("\"><tt>");
3235 sb.append(RequestUtil.filter(resourceName));
3237 sb.append("</tt></a></td>\r\n");
3239 sb.append("<td align=\"right\"><tt>");
3240 sb.append(" ");
3241 sb.append("</tt></td>\r\n");
3243 sb.append("<td align=\"right\"><tt>");
3244 sb.append(getLastModifiedHttp(folder.getAuditInfo()));
3245 sb.append("</tt></td>\r\n");
3247 sb.append("</tr>\r\n");
3249 List<FileHeaderDTO> files;
3251 User user = getUser(req);
3252 files = getService().getFiles(user.getId(), folder.getId(), true);
3253 } catch (ObjectNotFoundException e) {
3254 throw new ServletException(e.getMessage());
3255 } catch (InsufficientPermissionsException e) {
3256 throw new ServletException(e.getMessage());
3257 } catch (RpcException e) {
3258 throw new ServletException(e.getMessage());
3260 for (FileHeaderDTO file : files) {
3261 String resourceName = file.getName();
3262 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
3267 sb.append(" bgcolor=\"#eeeeee\"");
3271 sb.append("<td align=\"left\"> \r\n");
3272 sb.append("<a href=\"");
3273 sb.append(rewrittenContextPath);
3274 sb.append(rewriteUrl(path + resourceName));
3275 sb.append("\"><tt>");
3276 sb.append(RequestUtil.filter(resourceName));
3277 sb.append("</tt></a></td>\r\n");
3279 sb.append("<td align=\"right\"><tt>");
3280 sb.append(renderSize(file.getFileSize()));
3281 sb.append("</tt></td>\r\n");
3283 sb.append("<td align=\"right\"><tt>");
3284 sb.append(getLastModifiedHttp(file.getAuditInfo()));
3285 sb.append("</tt></td>\r\n");
3287 sb.append("</tr>\r\n");
3290 // Render the page footer
3291 sb.append("</table>\r\n");
3293 sb.append("<HR size=\"1\" noshade=\"noshade\">");
3295 sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
3296 sb.append("</body>\r\n");
3297 sb.append("</html>\r\n");
3299 // Return an input stream to the underlying bytes
3300 writer.write(sb.toString());
3302 return new ByteArrayInputStream(stream.toByteArray());
3307 * Render the specified file size (in bytes).
3309 * @param size File size (in bytes)
3310 * @return the size as a string
3312 private String renderSize(long size) {
3313 long leftSide = size / 1024;
3314 long rightSide = size % 1024 / 103; // Makes 1 digit
3315 if (leftSide == 0 && rightSide == 0 && size > 0)
3317 return "" + leftSide + "." + rightSide + " kb";
3323 * @param req Servlet request
3324 * @param resp Servlet response
3325 * @return boolean true if the copy is successful
3326 * @throws IOException
3328 private boolean copyResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
3329 // Parsing destination header
3330 String destinationPath = req.getHeader("Destination");
3331 if (destinationPath == null) {
3332 resp.sendError(WebdavStatus.SC_BAD_REQUEST);
3336 // Remove url encoding from destination
3337 destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
3339 int protocolIndex = destinationPath.indexOf("://");
3340 if (protocolIndex >= 0) {
3341 // if the Destination URL contains the protocol, we can safely
3342 // trim everything upto the first "/" character after "://"
3343 int firstSeparator = destinationPath.indexOf("/", protocolIndex + 4);
3344 if (firstSeparator < 0)
3345 destinationPath = "/";
3347 destinationPath = destinationPath.substring(firstSeparator);
3349 String hostName = req.getServerName();
3350 if (hostName != null && destinationPath.startsWith(hostName))
3351 destinationPath = destinationPath.substring(hostName.length());
3353 int portIndex = destinationPath.indexOf(":");
3355 destinationPath = destinationPath.substring(portIndex);
3357 if (destinationPath.startsWith(":")) {
3358 int firstSeparator = destinationPath.indexOf("/");
3359 if (firstSeparator < 0)
3360 destinationPath = "/";
3362 destinationPath = destinationPath.substring(firstSeparator);
3366 // Normalize destination path (remove '.' and '..')
3367 destinationPath = RequestUtil.normalize(destinationPath);
3369 String contextPath = req.getContextPath();
3370 if (contextPath != null && destinationPath.startsWith(contextPath))
3371 destinationPath = destinationPath.substring(contextPath.length());
3373 String pathInfo = req.getPathInfo();
3374 if (pathInfo != null) {
3375 String servletPath = req.getServletPath();
3376 if (servletPath != null && destinationPath.startsWith(servletPath))
3377 destinationPath = destinationPath.substring(servletPath.length());
3380 if (logger.isDebugEnabled())
3381 logger.debug("Dest path :" + destinationPath);
3383 if (destinationPath.toUpperCase().startsWith("/WEB-INF") || destinationPath.toUpperCase().startsWith("/META-INF")) {
3384 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3388 String path = getRelativePath(req);
3390 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3391 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3395 if (destinationPath.equals(path)) {
3396 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3400 // Parsing overwrite header
3401 boolean overwrite = true;
3402 String overwriteHeader = req.getHeader("Overwrite");
3404 if (overwriteHeader != null)
3405 if (overwriteHeader.equalsIgnoreCase("T"))
3410 User user = getUser(req);
3411 // Overwriting the destination
3412 boolean exists = true;
3414 getService().getResourceAtPath(user.getId(), destinationPath, true);
3415 } catch (ObjectNotFoundException e) {
3417 } catch (RpcException e) {
3418 resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
3423 // Delete destination resource, if it exists
3425 if (!deleteResource(destinationPath, req, resp, true))
3428 resp.setStatus(WebdavStatus.SC_CREATED);
3429 } else // If the destination exists, then it's a conflict
3431 resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
3434 resp.setStatus(WebdavStatus.SC_CREATED);
3436 // Copying source to destination.
3437 Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3440 result = copyResource(errorList, path, destinationPath, req);
3441 } catch (RpcException e) {
3442 resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
3445 if (!result || !errorList.isEmpty()) {
3446 sendReport(req, resp, errorList);
3449 // Removing any lock-null resource which would be present at
3450 // the destination path.
3451 lockNullResources.remove(destinationPath);
3456 * Copy a collection.
3458 * @param errorList Hashtable containing the list of errors which occurred
3459 * during the copy operation
3460 * @param source Path of the resource to be copied
3461 * @param theDest Destination path
3462 * @param req the HTTP request
3463 * @return boolean true if the copy is successful
3464 * @throws RpcException
3466 private boolean copyResource(Hashtable<String, Integer> errorList, String source, String theDest, HttpServletRequest req) throws RpcException {
3468 String dest = theDest;
3469 // Fix the destination path when copying collections.
3470 if (source.endsWith("/") && !dest.endsWith("/"))
3473 if (logger.isDebugEnabled())
3474 logger.debug("Copy: " + source + " To: " + dest);
3476 User user = getUser(req);
3477 Object object = null;
3479 object = getService().getResourceAtPath(user.getId(), source, true);
3480 } catch (ObjectNotFoundException e) {
3483 if (object instanceof FolderDTO) {
3484 FolderDTO folder = (FolderDTO) object;
3486 getService().copyFolder(user.getId(), folder.getId(), dest);
3487 } catch (ObjectNotFoundException e) {
3488 errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3490 } catch (DuplicateNameException e) {
3491 errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3493 } catch (InsufficientPermissionsException e) {
3494 errorList.put(dest, new Integer(WebdavStatus.SC_FORBIDDEN));
3499 String newSource = source;
3500 if (!source.endsWith("/"))
3502 String newDest = dest;
3503 if (!dest.endsWith("/"))
3505 // Recursively copy the subfolders.
3506 Iterator iter = folder.getSubfolders().iterator();
3507 while (iter.hasNext()) {
3508 FolderDTO subf = (FolderDTO) iter.next();
3509 String resourceName = subf.getName();
3510 copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3512 // Recursively copy the files.
3513 List<FileHeaderDTO> files;
3514 files = getService().getFiles(user.getId(), folder.getId(), true);
3515 for (FileHeaderDTO file : files) {
3516 String resourceName = file.getName();
3517 copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3519 } catch (RpcException e) {
3520 errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3522 } catch (ObjectNotFoundException e) {
3523 errorList.put(source, new Integer(WebdavStatus.SC_NOT_FOUND));
3525 } catch (InsufficientPermissionsException e) {
3526 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3530 } else if (object instanceof FileHeaderDTO) {
3531 FileHeaderDTO file = (FileHeaderDTO) object;
3533 getService().copyFile(user.getId(), file.getId(), dest);
3534 } catch (ObjectNotFoundException e) {
3535 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3537 } catch (DuplicateNameException e) {
3538 errorList.put(source, new Integer(WebdavStatus.SC_CONFLICT));
3540 } catch (InsufficientPermissionsException e) {
3541 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3543 } catch (QuotaExceededException e) {
3544 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3546 } catch (GSSIOException e) {
3547 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3551 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3558 * Delete a resource.
3560 * @param req Servlet request
3561 * @param resp Servlet response
3562 * @return boolean true if the deletion is successful
3563 * @throws IOException
3565 private boolean deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
3566 String path = getRelativePath(req);
3567 return deleteResource(path, req, resp, true);
3571 * Delete a resource.
3573 * @param path Path of the resource which is to be deleted
3574 * @param req Servlet request
3575 * @param resp Servlet response
3576 * @param setStatus Should the response status be set on successful
3578 * @return boolean true if the deletion is successful
3579 * @throws IOException
3581 private boolean deleteResource(String path, HttpServletRequest req, HttpServletResponse resp, boolean setStatus) throws IOException {
3582 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3583 resp.sendError(WebdavStatus.SC_FORBIDDEN);
3586 String ifHeader = req.getHeader("If");
3587 if (ifHeader == null)
3590 String lockTokenHeader = req.getHeader("Lock-Token");
3591 if (lockTokenHeader == null)
3592 lockTokenHeader = "";
3594 if (isLocked(path, ifHeader + lockTokenHeader)) {
3595 resp.sendError(WebdavStatus.SC_LOCKED);
3599 User user = getUser(req);
3600 boolean exists = true;
3601 Object object = null;
3603 object = getService().getResourceAtPath(user.getId(), path, true);
3604 } catch (ObjectNotFoundException e) {
3606 } catch (RpcException e) {
3607 resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3612 resp.sendError(WebdavStatus.SC_NOT_FOUND);
3616 FolderDTO folder = null;
3617 FileHeaderDTO file = null;
3618 if (object instanceof FolderDTO)
3619 folder = (FolderDTO) object;
3621 file = (FileHeaderDTO) object;
3625 getService().deleteFile(user.getId(), file.getId());
3626 } catch (InsufficientPermissionsException e) {
3627 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
3629 } catch (ObjectNotFoundException e) {
3630 // Although we had already found the object, it was
3631 // probably deleted from another thread.
3632 resp.sendError(WebdavStatus.SC_NOT_FOUND);
3634 } catch (RpcException e) {
3635 resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3638 else if (folder != null) {
3639 Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3640 deleteCollection(req, folder, path, errorList);
3642 getService().deleteFolder(user.getId(), folder.getId());
3643 } catch (InsufficientPermissionsException e) {
3644 errorList.put(path, new Integer(WebdavStatus.SC_METHOD_NOT_ALLOWED));
3645 } catch (ObjectNotFoundException e) {
3646 errorList.put(path, new Integer(WebdavStatus.SC_NOT_FOUND));
3647 } catch (RpcException e) {
3648 errorList.put(path, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3651 if (!errorList.isEmpty()) {
3652 sendReport(req, resp, errorList);
3657 resp.setStatus(WebdavStatus.SC_NO_CONTENT);
3662 * Deletes a collection.
3664 * @param req the HTTP request
3665 * @param folder the folder whose contents will be deleted
3666 * @param path Path to the collection to be deleted
3667 * @param errorList Contains the list of the errors which occurred
3669 private void deleteCollection(HttpServletRequest req, FolderDTO folder, String path, Hashtable<String, Integer> errorList) {
3671 if (logger.isDebugEnabled())
3672 logger.debug("Delete:" + path);
3674 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3675 errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
3679 String ifHeader = req.getHeader("If");
3680 if (ifHeader == null)
3683 String lockTokenHeader = req.getHeader("Lock-Token");
3684 if (lockTokenHeader == null)
3685 lockTokenHeader = "";
3687 Iterator iter = folder.getSubfolders().iterator();
3688 while (iter.hasNext()) {
3689 FolderDTO subf = (FolderDTO) iter.next();
3690 String childName = path;
3691 if (!childName.equals("/"))
3693 childName += subf.getName();
3695 if (isLocked(childName, ifHeader + lockTokenHeader))
3696 errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
3699 User user = getUser(req);
3700 Object object = getService().getResourceAtPath(user.getId(), childName, true);
3701 FolderDTO childFolder = null;
3702 FileHeaderDTO childFile = null;
3703 if (object instanceof FolderDTO)
3704 childFolder = (FolderDTO) object;
3706 childFile = (FileHeaderDTO) object;
3707 if (childFolder != null) {
3708 deleteCollection(req, childFolder, childName, errorList);
3709 getService().deleteFolder(user.getId(), childFolder.getId());
3710 } else if (childFile != null)
3711 getService().deleteFile(user.getId(), childFile.getId());
3712 } catch (ObjectNotFoundException e) {
3713 errorList.put(childName, new Integer(WebdavStatus.SC_NOT_FOUND));
3714 } catch (InsufficientPermissionsException e) {
3715 errorList.put(childName, new Integer(WebdavStatus.SC_FORBIDDEN));
3716 } catch (RpcException e) {
3717 errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3723 * Send a multistatus element containing a complete error report to the
3726 * @param req Servlet request
3727 * @param resp Servlet response
3728 * @param errorList List of error to be displayed
3729 * @throws IOException
3731 private void sendReport(HttpServletRequest req, HttpServletResponse resp, Hashtable errorList) throws IOException {
3733 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
3735 String absoluteUri = req.getRequestURI();
3736 String relativePath = getRelativePath(req);
3738 XMLWriter generatedXML = new XMLWriter();
3739 generatedXML.writeXMLHeader();
3741 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
3743 Enumeration pathList = errorList.keys();
3744 while (pathList.hasMoreElements()) {
3746 String errorPath = (String) pathList.nextElement();
3747 int errorCode = ((Integer) errorList.get(errorPath)).intValue();
3749 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
3751 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
3752 String toAppend = errorPath.substring(relativePath.length());
3753 if (!toAppend.startsWith("/"))
3754 toAppend = "/" + toAppend;
3755 generatedXML.writeText(absoluteUri + toAppend);
3756 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
3757 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
3758 generatedXML.writeText("HTTP/1.1 " + errorCode + " " + WebdavStatus.getStatusText(errorCode));
3759 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
3761 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
3765 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
3767 Writer writer = resp.getWriter();
3768 writer.write(generatedXML.toString());
3773 // --------------------------------------------- WebdavResolver Inner Class
3775 * Work around for XML parsers that don't fully respect
3776 * {@link DocumentBuilderFactory#setExpandEntityReferences(boolean)}.
3777 * External references are filtered out for security reasons. See
3780 private class WebdavResolver implements EntityResolver {
3783 * A private copy of the servlet context.
3785 private ServletContext context;
3788 * Construct the resolver by passing the servlet context.
3790 * @param theContext the servlet context
3792 public WebdavResolver(ServletContext theContext) {
3793 context = theContext;
3796 public InputSource resolveEntity(String publicId, String systemId) {
3797 context.log("The request included a reference to an external entity with PublicID " + publicId + " and SystemID " + systemId + " which was ignored");
3798 return new InputSource(new StringReader("Ignored external entity"));
3803 * Returns the user making the request. This is the user whose credentials
3804 * were supplied in the authorization header.
3806 * @param req the HTTP request
3807 * @return the user making the request
3809 protected User getUser(HttpServletRequest req) {
3810 return (User) req.getAttribute(USER_ATTRIBUTE);
3814 * Retrieves the user who owns the requested namespace, as specified in the
3817 * @param req the HTTP request
3818 * @return the owner of the namespace
3820 protected User getOwner(HttpServletRequest req) {
3821 return (User) req.getAttribute(OWNER_ATTRIBUTE);