79ec81b83604299a5f0ceebe93798c29294feaa0
[pithos] / gss / src / gr / ebs / gss / server / webdav / Webdav.java
1 /*
2  * Copyright 2007, 2008, 2009 Electronic Business Systems Ltd.
3  *
4  * This file is part of GSS.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19 package gr.ebs.gss.server.webdav;
20
21 import gr.ebs.gss.client.exceptions.DuplicateNameException;
22 import gr.ebs.gss.client.exceptions.GSSIOException;
23 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
24 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
25 import gr.ebs.gss.client.exceptions.QuotaExceededException;
26 import gr.ebs.gss.client.exceptions.RpcException;
27 import gr.ebs.gss.server.domain.User;
28 import gr.ebs.gss.server.domain.dto.AuditInfoDTO;
29 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
30 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
31 import gr.ebs.gss.server.domain.dto.FolderDTO;
32 import gr.ebs.gss.server.ejb.ExternalAPI;
33
34 import java.io.BufferedInputStream;
35 import java.io.ByteArrayInputStream;
36 import java.io.ByteArrayOutputStream;
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.io.InputStreamReader;
42 import java.io.OutputStreamWriter;
43 import java.io.PrintWriter;
44 import java.io.RandomAccessFile;
45 import java.io.Reader;
46 import java.io.StringReader;
47 import java.io.StringWriter;
48 import java.io.Writer;
49 import java.security.MessageDigest;
50 import java.security.NoSuchAlgorithmException;
51 import java.text.SimpleDateFormat;
52 import java.util.ArrayList;
53 import java.util.Date;
54 import java.util.Enumeration;
55 import java.util.Hashtable;
56 import java.util.Iterator;
57 import java.util.List;
58 import java.util.Locale;
59 import java.util.Stack;
60 import java.util.StringTokenizer;
61 import java.util.TimeZone;
62 import java.util.Vector;
63
64 import javax.naming.Context;
65 import javax.naming.InitialContext;
66 import javax.naming.NamingException;
67 import javax.rmi.PortableRemoteObject;
68 import javax.servlet.ServletContext;
69 import javax.servlet.ServletException;
70 import javax.servlet.ServletOutputStream;
71 import javax.servlet.UnavailableException;
72 import javax.servlet.http.HttpServlet;
73 import javax.servlet.http.HttpServletRequest;
74 import javax.servlet.http.HttpServletResponse;
75 import javax.xml.parsers.DocumentBuilder;
76 import javax.xml.parsers.DocumentBuilderFactory;
77 import javax.xml.parsers.ParserConfigurationException;
78
79 import org.apache.commons.httpclient.HttpStatus;
80 import org.apache.commons.logging.Log;
81 import org.apache.commons.logging.LogFactory;
82 import org.w3c.dom.Document;
83 import org.w3c.dom.Element;
84 import org.w3c.dom.Node;
85 import org.w3c.dom.NodeList;
86 import org.xml.sax.EntityResolver;
87 import org.xml.sax.InputSource;
88 import org.xml.sax.SAXException;
89
90 /**
91  * The implementation of the WebDAV service.
92  *
93  * @author past
94  */
95 public class Webdav extends HttpServlet {
96
97         /**
98          * The request attribute containing the user who owns the requested
99          * namespace.
100          */
101         protected static final String OWNER_ATTRIBUTE = "owner";
102
103         /**
104          * The request attribute containing the user making the request.
105          */
106         protected static final String USER_ATTRIBUTE = "user";
107
108         /**
109          * The logger.
110          */
111         private static Log logger = LogFactory.getLog(Webdav.class);
112
113         /**
114      *
115      */
116         protected static final String METHOD_GET = "GET";
117
118         /**
119      *
120      */
121         protected static final String METHOD_POST = "POST";
122
123         /**
124      *
125      */
126         protected static final String METHOD_PUT = "PUT";
127
128         /**
129      *
130      */
131         protected static final String METHOD_DELETE = "DELETE";
132
133         /**
134      *
135      */
136         protected static final String METHOD_HEAD = "HEAD";
137
138         /**
139      *
140      */
141         private static final String METHOD_OPTIONS = "OPTIONS";
142
143         /**
144      *
145      */
146         private static final String METHOD_PROPFIND = "PROPFIND";
147
148         /**
149      *
150      */
151         private static final String METHOD_PROPPATCH = "PROPPATCH";
152
153         /**
154      *
155      */
156         private static final String METHOD_MKCOL = "MKCOL";
157
158         /**
159      *
160      */
161         private static final String METHOD_COPY = "COPY";
162
163         /**
164      *
165      */
166         private static final String METHOD_MOVE = "MOVE";
167
168         /**
169      *
170      */
171         private static final String METHOD_LOCK = "LOCK";
172
173         /**
174      *
175      */
176         private static final String METHOD_UNLOCK = "UNLOCK";
177
178         /**
179          * Default depth is infinite.
180          */
181         static final int INFINITY = 3; // To limit tree browsing a bit
182
183         /**
184          * PROPFIND - Specify a property mask.
185          */
186         private static final int FIND_BY_PROPERTY = 0;
187
188         /**
189          * PROPFIND - Display all properties.
190          */
191         private static final int FIND_ALL_PROP = 1;
192
193         /**
194          * PROPFIND - Return property names.
195          */
196         private static final int FIND_PROPERTY_NAMES = 2;
197
198         /**
199          * Default namespace.
200          */
201         private static final String DEFAULT_NAMESPACE = "DAV:";
202
203         /**
204          * Create a new lock.
205          */
206         private static final int LOCK_CREATION = 0;
207
208         /**
209          * Refresh lock.
210          */
211         private static final int LOCK_REFRESH = 1;
212
213         /**
214          * Default lock timeout value.
215          */
216         private static final int DEFAULT_TIMEOUT = 3600;
217
218         /**
219          * Maximum lock timeout.
220          */
221         private static final int MAX_TIMEOUT = 604800;
222
223         /**
224          * Size of file transfer buffer in bytes.
225          */
226         private static final int BUFFER_SIZE = 4096;
227
228         /**
229          * The output buffer size to use when serving resources.
230          */
231         protected int output = 2048;
232
233         /**
234          * The input buffer size to use when serving resources.
235          */
236         private int input = 2048;
237
238         /**
239          * MIME multipart separation string
240          */
241         protected static final String mimeSeparation = "GSS_MIME_BOUNDARY";
242
243         /**
244          * Simple date format for the creation date ISO representation (partial).
245          */
246         private static final SimpleDateFormat creationDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
247
248         /**
249          * HTTP date format.
250          */
251         private static final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
252
253         /**
254          * Array containing the safe characters set.
255          */
256         private static URLEncoder urlEncoder;
257
258         /**
259          * File encoding to be used when reading static files. If none is specified
260          * the platform default is used.
261          */
262         private String fileEncoding = null;
263
264         /**
265          * The style sheet for displaying the directory listings.
266          */
267         private static final String GSS_CSS = "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " + "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " + "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " + "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " + "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " + "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" + "A {color : black;}" + "A.name {color : black;}" + "HR {color : #525D76;}";
268
269         /**
270          * Secret information used to generate reasonably secure lock ids.
271          */
272         private String secret = "gss-webdav";
273
274         /**
275          * Repository of the locks put on single resources.
276          * <p>
277          * Key : path <br>
278          * Value : LockInfo
279          */
280         private Hashtable<String, LockInfo> resourceLocks = new Hashtable<String, LockInfo>();
281
282         /**
283          * Repository of the lock-null resources.
284          * <p>
285          * Key : path of the collection containing the lock-null resource<br>
286          * Value : Vector of lock-null resource which are members of the collection.
287          * Each element of the Vector is the path associated with the lock-null
288          * resource.
289          */
290         private Hashtable<String, Vector<String>> lockNullResources = new Hashtable<String, Vector<String>>();
291
292         /**
293          * Vector of the heritable locks.
294          * <p>
295          * Key : path <br>
296          * Value : LockInfo
297          */
298         private Vector<LockInfo> collectionLocks = new Vector<LockInfo>();
299
300         /**
301          * Full range marker.
302          */
303         protected static ArrayList FULL = new ArrayList();
304
305         /**
306          * MD5 message digest provider.
307          */
308         protected static MessageDigest md5Helper;
309
310         /**
311          * The MD5 helper object for this class.
312          */
313         protected static final MD5Encoder md5Encoder = new MD5Encoder();
314
315         /**
316          * GMT timezone - all HTTP dates are on GMT
317          */
318         static {
319                 creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
320                 urlEncoder = new URLEncoder();
321                 urlEncoder.addSafeCharacter('-');
322                 urlEncoder.addSafeCharacter('_');
323                 urlEncoder.addSafeCharacter('.');
324                 urlEncoder.addSafeCharacter('*');
325                 urlEncoder.addSafeCharacter('/');
326         }
327
328         @Override
329         public void init() throws ServletException {
330                 if (getServletConfig().getInitParameter("input") != null)
331                         input = Integer.parseInt(getServletConfig().getInitParameter("input"));
332
333                 if (getServletConfig().getInitParameter("output") != null)
334                         output = Integer.parseInt(getServletConfig().getInitParameter("output"));
335
336                 fileEncoding = getServletConfig().getInitParameter("fileEncoding");
337
338                 // Sanity check on the specified buffer sizes
339                 if (input < 256)
340                         input = 256;
341                 if (output < 256)
342                         output = 256;
343                 if (logger.isDebugEnabled())
344                         logger.debug("Input buffer size=" + input + ", output buffer size=" + output);
345
346                 if (getServletConfig().getInitParameter("secret") != null)
347                         secret = getServletConfig().getInitParameter("secret");
348
349                 // Load the MD5 helper used to calculate signatures.
350                 try {
351                         md5Helper = MessageDigest.getInstance("MD5");
352                 } catch (NoSuchAlgorithmException e) {
353                         throw new UnavailableException("No MD5");
354                 }
355         }
356
357         /**
358          * A helper method that retrieves a reference to the ExternalAPI bean and
359          * stores it for future use.
360          *
361          * @return an ExternalAPI instance
362          * @throws RpcException in case an error occurs
363          */
364         protected ExternalAPI getService() throws RpcException {
365                 try {
366                         final Context ctx = new InitialContext();
367                         final Object ref = ctx.lookup("gss/ExternalAPIBean/local");
368                         return (ExternalAPI) PortableRemoteObject.narrow(ref, ExternalAPI.class);
369                 } catch (final NamingException e) {
370                         logger.error("Unable to retrieve the ExternalAPI EJB", e);
371                         throw new RpcException("An error occurred while contacting the naming service");
372                 }
373         }
374
375         @Override
376         public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
377                 String method = request.getMethod();
378
379                 if (logger.isDebugEnabled()) {
380                         String path = request.getPathInfo();
381                         if (path == null)
382                                 path = request.getServletPath();
383                         if (path == null || path.equals(""))
384                                 path = "/";
385                         logger.debug("[" + method + "] " + path);
386                 }
387
388                 try {
389                         User user = null;
390                         if (request.getUserPrincipal() != null) { // Let unauthenticated
391                                                                                                                 // OPTIONS go through;
392                                                                                                                 // all others will be
393                                                                                                                 // blocked by
394                                                                                                                 // authentication anyway
395                                                                                                                 // before we get here.
396                                 user = getService().findUser(request.getUserPrincipal().getName());
397                                 if (user == null) {
398                                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
399                                         return;
400                                 }
401                         }
402                         request.setAttribute(USER_ATTRIBUTE, user);
403                         request.setAttribute(OWNER_ATTRIBUTE, user);
404                 } catch (RpcException e) {
405                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
406                         return;
407                 }
408                 if (method.equals(METHOD_GET))
409                         doGet(request, response);
410                 else if (method.equals(METHOD_POST))
411                         doPost(request, response);
412                 else if (method.equals(METHOD_PUT))
413                         doPut(request, response);
414                 else if (method.equals(METHOD_DELETE))
415                         doDelete(request, response);
416                 else if (method.equals(METHOD_HEAD))
417                         doHead(request, response);
418                 else if (method.equals(METHOD_PROPFIND))
419                         doPropfind(request, response);
420                 else if (method.equals(METHOD_PROPPATCH))
421                         doProppatch(request, response);
422                 else if (method.equals(METHOD_MKCOL))
423                         doMkcol(request, response);
424                 else if (method.equals(METHOD_COPY))
425                         doCopy(request, response);
426                 else if (method.equals(METHOD_MOVE))
427                         doMove(request, response);
428                 else if (method.equals(METHOD_LOCK))
429                         doLock(request, response);
430                 else if (method.equals(METHOD_UNLOCK))
431                         doUnlock(request, response);
432                 else if (method.equals(METHOD_OPTIONS))
433                         doOptions(request, response);
434                 else
435                         // DefaultServlet processing for TRACE, etc.
436                         super.service(request, response);
437         }
438
439         @Override
440         protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException {
441                 resp.addHeader("DAV", "1,2");
442                 StringBuffer methodsAllowed = new StringBuffer();
443                 try {
444                         methodsAllowed = determineMethodsAllowed(req);
445                 } catch (RpcException e) {
446                         resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
447                         return;
448                 }
449                 resp.addHeader("Allow", methodsAllowed.toString());
450                 resp.addHeader("MS-Author-Via", "DAV");
451         }
452
453         /**
454          * Implement the PROPFIND method.
455          *
456          * @param req the HTTP request
457          * @param resp the HTTP response
458          * @throws ServletException
459          * @throws IOException
460          */
461         private void doPropfind(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
462                 String path = getRelativePath(req);
463                 if (path.endsWith("/") && !path.equals("/"))
464                         path = path.substring(0, path.length() - 1);
465
466                 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
467                         resp.sendError(WebdavStatus.SC_FORBIDDEN);
468                         return;
469                 }
470
471                 // Properties which are to be displayed.
472                 Vector<String> properties = null;
473                 // Propfind depth
474                 int depth = INFINITY;
475                 // Propfind type
476                 int type = FIND_ALL_PROP;
477
478                 String depthStr = req.getHeader("Depth");
479
480                 if (depthStr == null)
481                         depth = INFINITY;
482                 else if (depthStr.equals("0"))
483                         depth = 0;
484                 else if (depthStr.equals("1"))
485                         depth = 1;
486                 else if (depthStr.equals("infinity"))
487                         depth = INFINITY;
488
489                 Node propNode = null;
490
491                 if (req.getInputStream().available() > 0) {
492                         DocumentBuilder documentBuilder = getDocumentBuilder();
493
494                         try {
495                                 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
496
497                                 // Get the root element of the document
498                                 Element rootElement = document.getDocumentElement();
499                                 NodeList childList = rootElement.getChildNodes();
500
501                                 for (int i = 0; i < childList.getLength(); i++) {
502                                         Node currentNode = childList.item(i);
503                                         switch (currentNode.getNodeType()) {
504                                                 case Node.TEXT_NODE:
505                                                         break;
506                                                 case Node.ELEMENT_NODE:
507                                                         if (currentNode.getNodeName().endsWith("prop")) {
508                                                                 type = FIND_BY_PROPERTY;
509                                                                 propNode = currentNode;
510                                                         }
511                                                         if (currentNode.getNodeName().endsWith("propname"))
512                                                                 type = FIND_PROPERTY_NAMES;
513                                                         if (currentNode.getNodeName().endsWith("allprop"))
514                                                                 type = FIND_ALL_PROP;
515                                                         break;
516                                         }
517                                 }
518                         } catch (SAXException e) {
519                                 // Something went wrong - use the defaults.
520                                 if (logger.isDebugEnabled())
521                                         logger.debug(e.getMessage());
522                         } catch (IOException e) {
523                                 // Something went wrong - use the defaults.
524                                 if (logger.isDebugEnabled())
525                                         logger.debug(e.getMessage());
526                         }
527                 }
528
529                 if (type == FIND_BY_PROPERTY) {
530                         properties = new Vector<String>();
531                         NodeList childList = propNode.getChildNodes();
532
533                         for (int i = 0; i < childList.getLength(); i++) {
534                                 Node currentNode = childList.item(i);
535                                 switch (currentNode.getNodeType()) {
536                                         case Node.TEXT_NODE:
537                                                 break;
538                                         case Node.ELEMENT_NODE:
539                                                 String nodeName = currentNode.getNodeName();
540                                                 String propertyName = null;
541                                                 if (nodeName.indexOf(':') != -1)
542                                                         propertyName = nodeName.substring(nodeName.indexOf(':') + 1);
543                                                 else
544                                                         propertyName = nodeName;
545                                                 // href is a live property which is handled differently
546                                                 properties.addElement(propertyName);
547                                                 break;
548                                 }
549                         }
550                 }
551                 User user = getUser(req);
552                 boolean exists = true;
553                 Object object = null;
554                 try {
555                         object = getService().getResourceAtPath(user.getId(), path, true);
556                 } catch (ObjectNotFoundException e) {
557                         exists = false;
558                         int slash = path.lastIndexOf('/');
559                         if (slash != -1) {
560                                 String parentPath = path.substring(0, slash);
561                                 Vector currentLockNullResources = lockNullResources.get(parentPath);
562                                 if (currentLockNullResources != null) {
563                                         Enumeration lockNullResourcesList = currentLockNullResources.elements();
564                                         while (lockNullResourcesList.hasMoreElements()) {
565                                                 String lockNullPath = (String) lockNullResourcesList.nextElement();
566                                                 if (lockNullPath.equals(path)) {
567                                                         resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
568                                                         resp.setContentType("text/xml; charset=UTF-8");
569                                                         // Create multistatus object
570                                                         XMLWriter generatedXML = new XMLWriter(resp.getWriter());
571                                                         generatedXML.writeXMLHeader();
572                                                         generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
573                                                         parseLockNullProperties(req, generatedXML, lockNullPath, type, properties);
574                                                         generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
575                                                         generatedXML.sendData();
576                                                         return;
577                                                 }
578                                         }
579                                 }
580                         }
581                 } catch (RpcException e) {
582                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
583                         return;
584                 }
585                 if (!exists) {
586                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
587                         return;
588                 }
589                 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
590                 resp.setContentType("text/xml; charset=UTF-8");
591                 // Create multistatus object
592                 XMLWriter generatedXML = new XMLWriter(resp.getWriter());
593                 generatedXML.writeXMLHeader();
594                 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
595                 if (depth == 0)
596                         parseProperties(req, generatedXML, path, type, properties, object);
597                 else {
598                         // The stack always contains the object of the current level
599                         Stack<String> stack = new Stack<String>();
600                         stack.push(path);
601
602                         // Stack of the objects one level below
603                         Stack<String> stackBelow = new Stack<String>();
604                         while (!stack.isEmpty() && depth >= 0) {
605                                 String currentPath = stack.pop();
606                                 try {
607                                         object = getService().getResourceAtPath(user.getId(), currentPath, true);
608                                 } catch (ObjectNotFoundException e) {
609                                         continue;
610                                 } catch (RpcException e) {
611                                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
612                                         return;
613                                 }
614                                 parseProperties(req, generatedXML, currentPath, type, properties, object);
615                                 if (object instanceof FolderDTO && depth > 0) {
616                                         FolderDTO folder = (FolderDTO) object;
617                                         // Retrieve the subfolders.
618                                         List subfolders = folder.getSubfolders();
619                                         Iterator iter = subfolders.iterator();
620                                         while (iter.hasNext()) {
621                                                 FolderDTO f = (FolderDTO) iter.next();
622                                                 String newPath = currentPath;
623                                                 if (!newPath.endsWith("/"))
624                                                         newPath += "/";
625                                                 newPath += f.getName();
626                                                 stackBelow.push(newPath);
627                                         }
628                                         // Retrieve the files.
629                                         List<FileHeaderDTO> files;
630                                         try {
631                                                 files = getService().getFiles(user.getId(), folder.getId(), true);
632                                         } catch (ObjectNotFoundException e) {
633                                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
634                                                 return;
635                                         } catch (InsufficientPermissionsException e) {
636                                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN, path);
637                                                 return;
638                                         } catch (RpcException e) {
639                                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
640                                                 return;
641                                         }
642                                         for (FileHeaderDTO file : files) {
643                                                 String newPath = currentPath;
644                                                 if (!newPath.endsWith("/"))
645                                                         newPath += "/";
646                                                 newPath += file.getName();
647                                                 stackBelow.push(newPath);
648                                         }
649                                         // Displaying the lock-null resources present in that
650                                         // collection
651                                         String lockPath = currentPath;
652                                         if (lockPath.endsWith("/"))
653                                                 lockPath = lockPath.substring(0, lockPath.length() - 1);
654                                         Vector currentLockNullResources = lockNullResources.get(lockPath);
655                                         if (currentLockNullResources != null) {
656                                                 Enumeration lockNullResourcesList = currentLockNullResources.elements();
657                                                 while (lockNullResourcesList.hasMoreElements()) {
658                                                         String lockNullPath = (String) lockNullResourcesList.nextElement();
659                                                         parseLockNullProperties(req, generatedXML, lockNullPath, type, properties);
660                                                 }
661                                         }
662                                 }
663                                 if (stack.isEmpty()) {
664                                         depth--;
665                                         stack = stackBelow;
666                                         stackBelow = new Stack<String>();
667                                 }
668                                 generatedXML.sendData();
669                         }
670                 }
671                 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
672                 generatedXML.sendData();
673         }
674
675         /**
676          * PROPPATCH Method.
677          *
678          * @param req the HTTP request
679          * @param resp the HTTP response
680          * @throws IOException if an error occurs while sending the response
681          */
682         private void doProppatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
683                 if (isLocked(req)) {
684                         resp.sendError(WebdavStatus.SC_LOCKED);
685                         return;
686                 }
687                 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
688         }
689
690         /* (non-Javadoc)
691          * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
692          */
693         @Override
694         protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
695                 if (isLocked(req)) {
696                         resp.sendError(WebdavStatus.SC_LOCKED);
697                         return;
698                 }
699                 deleteResource(req, resp);
700         }
701
702         @Override
703         protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
704                 // Serve the requested resource, including the data content
705                 try {
706                         serveResource(req, resp, true);
707                 } catch (ObjectNotFoundException e) {
708                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
709                         return;
710                 } catch (InsufficientPermissionsException e) {
711                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
712                         return;
713                 } catch (RpcException e) {
714                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
715                         return;
716                 }
717         }
718
719         @Override
720         protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
721                 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
722         }
723
724         @Override
725         protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
726                 if (isLocked(req)) {
727                         resp.sendError(WebdavStatus.SC_LOCKED);
728                         return;
729                 }
730
731                 User user = getUser(req);
732                 String path = getRelativePath(req);
733                 boolean exists = true;
734                 Object resource = null;
735                 FileHeaderDTO file = null;
736                 try {
737                         resource = getService().getResourceAtPath(user.getId(), path, true);
738                 } catch (ObjectNotFoundException e) {
739                         exists = false;
740                 } catch (RpcException e) {
741                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
742                         return;
743                 }
744
745                 if (exists)
746                         if (resource instanceof FileHeaderDTO)
747                                 file = (FileHeaderDTO) resource;
748                         else {
749                                 resp.sendError(HttpServletResponse.SC_CONFLICT);
750                                 return;
751                         }
752                 boolean result = true;
753
754                 // Temporary content file used to support partial PUT.
755                 File contentFile = null;
756
757                 Range range = parseContentRange(req, resp);
758
759                 InputStream resourceInputStream = null;
760
761                 // Append data specified in ranges to existing content for this
762                 // resource - create a temporary file on the local filesystem to
763                 // perform this operation.
764                 // Assume just one range is specified for now
765                 if (range != null) {
766                         try {
767                                 contentFile = executePartialPut(req, range, path);
768                         } catch (RpcException e) {
769                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
770                                 return;
771                         } catch (ObjectNotFoundException e) {
772                                 resp.sendError(HttpServletResponse.SC_CONFLICT);
773                                 return;
774                         } catch (InsufficientPermissionsException e) {
775                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
776                                 return;
777                         }
778                         resourceInputStream = new FileInputStream(contentFile);
779                 } else
780                         resourceInputStream = req.getInputStream();
781
782                 try {
783                         FolderDTO folder = null;
784                         Object parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
785                         if (!(parent instanceof FolderDTO)) {
786                                 resp.sendError(HttpServletResponse.SC_CONFLICT);
787                                 return;
788                         }
789                         folder = (FolderDTO) parent;
790                         String name = getLastElement(path);
791                         String mimeType = getServletContext().getMimeType(name);
792                         // FIXME: Add attributes
793                         if (exists)
794                                 getService().updateFileContents(user.getId(), file.getId(), mimeType, resourceInputStream);
795                         else
796                                 getService().createFile(user.getId(), folder.getId(), name, mimeType, resourceInputStream);
797                 } catch (ObjectNotFoundException e) {
798                         result = false;
799                 } catch (InsufficientPermissionsException e) {
800                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
801                         return;
802                 } catch (QuotaExceededException e) {
803                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
804                         return;
805                 } catch (GSSIOException e) {
806                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
807                         return;
808                 } catch (RpcException e) {
809                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
810                         return;
811                 } catch (DuplicateNameException e) {
812                         resp.sendError(HttpServletResponse.SC_CONFLICT);
813                         return;
814                 }
815
816                 if (result) {
817                         if (exists)
818                                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
819                         else
820                                 resp.setStatus(HttpServletResponse.SC_CREATED);
821                 } else
822                         resp.sendError(HttpServletResponse.SC_CONFLICT);
823
824                 // Removing any lock-null resource which would be present.
825                 lockNullResources.remove(path);
826         }
827
828         @Override
829         protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
830                 // Serve the requested resource, without the data content
831                 try {
832                         serveResource(req, resp, false);
833                 } catch (ObjectNotFoundException e) {
834                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
835                         return;
836                 } catch (InsufficientPermissionsException e) {
837                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
838                         return;
839                 } catch (RpcException e) {
840                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
841                         return;
842                 }
843         }
844
845         /**
846          * The UNLOCK method.
847          *
848          * @param req the HTTP request
849          * @param resp the HTTP response
850          * @throws IOException if an error occurs while sending the response
851          */
852         private void doUnlock(HttpServletRequest req, HttpServletResponse resp) throws IOException {
853                 if (isLocked(req)) {
854                         resp.sendError(WebdavStatus.SC_LOCKED);
855                         return;
856                 }
857                 String path = getRelativePath(req);
858                 String lockTokenHeader = req.getHeader("Lock-Token");
859                 if (lockTokenHeader == null)
860                         lockTokenHeader = "";
861
862                 // Checking resource locks
863                 LockInfo lock = resourceLocks.get(path);
864                 Enumeration tokenList = null;
865                 if (lock != null) {
866                         // At least one of the tokens of the locks must have been given
867                         tokenList = lock.tokens.elements();
868                         while (tokenList.hasMoreElements()) {
869                                 String token = (String) tokenList.nextElement();
870                                 if (lockTokenHeader.indexOf(token) != -1)
871                                         lock.tokens.removeElement(token);
872                         }
873                         if (lock.tokens.isEmpty()) {
874                                 resourceLocks.remove(path);
875                                 // Removing any lock-null resource which would be present
876                                 lockNullResources.remove(path);
877                         }
878                 }
879                 // Checking inheritable collection locks
880                 Enumeration collectionLocksList = collectionLocks.elements();
881                 while (collectionLocksList.hasMoreElements()) {
882                         lock = (LockInfo) collectionLocksList.nextElement();
883                         if (path.equals(lock.path)) {
884                                 tokenList = lock.tokens.elements();
885                                 while (tokenList.hasMoreElements()) {
886                                         String token = (String) tokenList.nextElement();
887                                         if (lockTokenHeader.indexOf(token) != -1) {
888                                                 lock.tokens.removeElement(token);
889                                                 break;
890                                         }
891                                 }
892                                 if (lock.tokens.isEmpty()) {
893                                         collectionLocks.removeElement(lock);
894                                         // Removing any lock-null resource which would be present
895                                         lockNullResources.remove(path);
896                                 }
897                         }
898                 }
899                 resp.setStatus(WebdavStatus.SC_NO_CONTENT);
900         }
901
902         /**
903          * The LOCK method.
904          *
905          * @param req the HTTP request
906          * @param resp the HTTP response
907          * @throws IOException if an error occurs while sending the response
908          * @throws ServletException
909          */
910         private void doLock(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
911                 if (isLocked(req)) {
912                         resp.sendError(WebdavStatus.SC_LOCKED);
913                         return;
914                 }
915
916                 LockInfo lock = new LockInfo();
917                 // Parsing lock request
918
919                 // Parsing depth header
920                 String depthStr = req.getHeader("Depth");
921                 if (depthStr == null)
922                         lock.depth = INFINITY;
923                 else if (depthStr.equals("0"))
924                         lock.depth = 0;
925                 else
926                         lock.depth = INFINITY;
927
928                 // Parsing timeout header
929                 int lockDuration = DEFAULT_TIMEOUT;
930                 String lockDurationStr = req.getHeader("Timeout");
931                 if (lockDurationStr == null)
932                         lockDuration = DEFAULT_TIMEOUT;
933                 else {
934                         int commaPos = lockDurationStr.indexOf(",");
935                         // If multiple timeouts, just use the first
936                         if (commaPos != -1)
937                                 lockDurationStr = lockDurationStr.substring(0, commaPos);
938                         if (lockDurationStr.startsWith("Second-"))
939                                 lockDuration = new Integer(lockDurationStr.substring(7)).intValue();
940                         else if (lockDurationStr.equalsIgnoreCase("infinity"))
941                                 lockDuration = MAX_TIMEOUT;
942                         else
943                                 try {
944                                         lockDuration = new Integer(lockDurationStr).intValue();
945                                 } catch (NumberFormatException e) {
946                                         lockDuration = MAX_TIMEOUT;
947                                 }
948                         if (lockDuration == 0)
949                                 lockDuration = DEFAULT_TIMEOUT;
950                         if (lockDuration > MAX_TIMEOUT)
951                                 lockDuration = MAX_TIMEOUT;
952                 }
953                 lock.expiresAt = System.currentTimeMillis() + lockDuration * 1000;
954
955                 int lockRequestType = LOCK_CREATION;
956                 Node lockInfoNode = null;
957                 DocumentBuilder documentBuilder = getDocumentBuilder();
958
959                 try {
960                         Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
961                         // Get the root element of the document
962                         Element rootElement = document.getDocumentElement();
963                         lockInfoNode = rootElement;
964                 } catch (IOException e) {
965                         lockRequestType = LOCK_REFRESH;
966                 } catch (SAXException e) {
967                         lockRequestType = LOCK_REFRESH;
968                 }
969
970                 if (lockInfoNode != null) {
971                         // Reading lock information
972                         NodeList childList = lockInfoNode.getChildNodes();
973                         StringWriter strWriter = null;
974                         DOMWriter domWriter = null;
975
976                         Node lockScopeNode = null;
977                         Node lockTypeNode = null;
978                         Node lockOwnerNode = null;
979
980                         for (int i = 0; i < childList.getLength(); i++) {
981                                 Node currentNode = childList.item(i);
982                                 switch (currentNode.getNodeType()) {
983                                         case Node.TEXT_NODE:
984                                                 break;
985                                         case Node.ELEMENT_NODE:
986                                                 String nodeName = currentNode.getNodeName();
987                                                 if (nodeName.endsWith("lockscope"))
988                                                         lockScopeNode = currentNode;
989                                                 if (nodeName.endsWith("locktype"))
990                                                         lockTypeNode = currentNode;
991                                                 if (nodeName.endsWith("owner"))
992                                                         lockOwnerNode = currentNode;
993                                                 break;
994                                 }
995                         }
996
997                         if (lockScopeNode != null) {
998                                 childList = lockScopeNode.getChildNodes();
999                                 for (int i = 0; i < childList.getLength(); i++) {
1000                                         Node currentNode = childList.item(i);
1001                                         switch (currentNode.getNodeType()) {
1002                                                 case Node.TEXT_NODE:
1003                                                         break;
1004                                                 case Node.ELEMENT_NODE:
1005                                                         String tempScope = currentNode.getNodeName();
1006                                                         if (tempScope.indexOf(':') != -1)
1007                                                                 lock.scope = tempScope.substring(tempScope.indexOf(':') + 1);
1008                                                         else
1009                                                                 lock.scope = tempScope;
1010                                                         break;
1011                                         }
1012                                 }
1013                                 if (lock.scope == null)
1014                                         // Bad request
1015                                         resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1016                         } else
1017                                 // Bad request
1018                                 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1019
1020                         if (lockTypeNode != null) {
1021                                 childList = lockTypeNode.getChildNodes();
1022                                 for (int i = 0; i < childList.getLength(); i++) {
1023                                         Node currentNode = childList.item(i);
1024                                         switch (currentNode.getNodeType()) {
1025                                                 case Node.TEXT_NODE:
1026                                                         break;
1027                                                 case Node.ELEMENT_NODE:
1028                                                         String tempType = currentNode.getNodeName();
1029                                                         if (tempType.indexOf(':') != -1)
1030                                                                 lock.type = tempType.substring(tempType.indexOf(':') + 1);
1031                                                         else
1032                                                                 lock.type = tempType;
1033                                                         break;
1034                                         }
1035                                 }
1036
1037                                 if (lock.type == null)
1038                                         // Bad request
1039                                         resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1040                         } else
1041                                 // Bad request
1042                                 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1043
1044                         if (lockOwnerNode != null) {
1045                                 childList = lockOwnerNode.getChildNodes();
1046                                 for (int i = 0; i < childList.getLength(); i++) {
1047                                         Node currentNode = childList.item(i);
1048                                         switch (currentNode.getNodeType()) {
1049                                                 case Node.TEXT_NODE:
1050                                                         lock.owner += currentNode.getNodeValue();
1051                                                         break;
1052                                                 case Node.ELEMENT_NODE:
1053                                                         strWriter = new StringWriter();
1054                                                         domWriter = new DOMWriter(strWriter, true);
1055                                                         domWriter.setQualifiedNames(false);
1056                                                         domWriter.print(currentNode);
1057                                                         lock.owner += strWriter.toString();
1058                                                         break;
1059                                         }
1060                                 }
1061
1062                                 if (lock.owner == null)
1063                                         // Bad request
1064                                         resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1065                         } else
1066                                 lock.owner = new String();
1067                 }
1068
1069                 String path = getRelativePath(req);
1070                 lock.path = path;
1071                 User user = getUser(req);
1072                 boolean exists = true;
1073                 Object object = null;
1074                 try {
1075                         object = getService().getResourceAtPath(user.getId(), path, true);
1076                 } catch (ObjectNotFoundException e) {
1077                         exists = false;
1078                 } catch (RpcException e) {
1079                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1080                         return;
1081                 }
1082
1083                 Enumeration locksList = null;
1084                 if (lockRequestType == LOCK_CREATION) {
1085                         // Generating lock id
1086                         String lockTokenStr = req.getServletPath() + "-" + lock.type + "-" + lock.scope + "-" + req.getUserPrincipal() + "-" + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-" + lock.expiresAt + "-" + System.currentTimeMillis() + "-" + secret;
1087                         String lockToken = md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
1088
1089                         if (exists && object instanceof FolderDTO && lock.depth == INFINITY) {
1090
1091                                 // Locking a collection (and all its member resources)
1092
1093                                 // Checking if a child resource of this collection is
1094                                 // already locked
1095                                 Vector<String> lockPaths = new Vector<String>();
1096                                 locksList = collectionLocks.elements();
1097                                 while (locksList.hasMoreElements()) {
1098                                         LockInfo currentLock = (LockInfo) locksList.nextElement();
1099                                         if (currentLock.hasExpired()) {
1100                                                 resourceLocks.remove(currentLock.path);
1101                                                 continue;
1102                                         }
1103                                         if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive()))
1104                                                 // A child collection of this collection is locked
1105                                                 lockPaths.addElement(currentLock.path);
1106                                 }
1107                                 locksList = resourceLocks.elements();
1108                                 while (locksList.hasMoreElements()) {
1109                                         LockInfo currentLock = (LockInfo) locksList.nextElement();
1110                                         if (currentLock.hasExpired()) {
1111                                                 resourceLocks.remove(currentLock.path);
1112                                                 continue;
1113                                         }
1114                                         if (currentLock.path.startsWith(lock.path) && (currentLock.isExclusive() || lock.isExclusive()))
1115                                                 // A child resource of this collection is locked
1116                                                 lockPaths.addElement(currentLock.path);
1117                                 }
1118
1119                                 if (!lockPaths.isEmpty()) {
1120                                         // One of the child paths was locked
1121                                         // We generate a multistatus error report
1122                                         Enumeration lockPathsList = lockPaths.elements();
1123                                         resp.setStatus(WebdavStatus.SC_CONFLICT);
1124                                         XMLWriter generatedXML = new XMLWriter();
1125                                         generatedXML.writeXMLHeader();
1126
1127                                         generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
1128
1129                                         while (lockPathsList.hasMoreElements()) {
1130                                                 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1131                                                 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1132                                                 generatedXML.writeText((String) lockPathsList.nextElement());
1133                                                 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1134                                                 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1135                                                 generatedXML.writeText("HTTP/1.1 " + WebdavStatus.SC_LOCKED + " " + WebdavStatus.getStatusText(WebdavStatus.SC_LOCKED));
1136                                                 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1137                                                 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1138                                         }
1139
1140                                         generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
1141
1142                                         Writer writer = resp.getWriter();
1143                                         writer.write(generatedXML.toString());
1144                                         writer.close();
1145                                         return;
1146                                 }
1147
1148                                 boolean addLock = true;
1149                                 // Checking if there is already a shared lock on this path
1150                                 locksList = collectionLocks.elements();
1151                                 while (locksList.hasMoreElements()) {
1152                                         LockInfo currentLock = (LockInfo) locksList.nextElement();
1153                                         if (currentLock.path.equals(lock.path)) {
1154                                                 if (currentLock.isExclusive()) {
1155                                                         resp.sendError(WebdavStatus.SC_LOCKED);
1156                                                         return;
1157                                                 } else if (lock.isExclusive()) {
1158                                                         resp.sendError(WebdavStatus.SC_LOCKED);
1159                                                         return;
1160                                                 }
1161                                                 currentLock.tokens.addElement(lockToken);
1162                                                 lock = currentLock;
1163                                                 addLock = false;
1164                                         }
1165                                 }
1166                                 if (addLock) {
1167                                         lock.tokens.addElement(lockToken);
1168                                         collectionLocks.addElement(lock);
1169                                 }
1170                         } else {
1171                                 // Locking a single resource
1172
1173                                 // Retrieving an already existing lock on that resource
1174                                 LockInfo presentLock = resourceLocks.get(lock.path);
1175                                 if (presentLock != null) {
1176                                         if (presentLock.isExclusive() || lock.isExclusive()) {
1177                                                 // If either lock is exclusive, the lock can't be
1178                                                 // granted.
1179                                                 resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
1180                                                 return;
1181                                         } else {
1182                                                 presentLock.tokens.addElement(lockToken);
1183                                                 lock = presentLock;
1184                                         }
1185
1186                                 } else {
1187                                         lock.tokens.addElement(lockToken);
1188                                         resourceLocks.put(lock.path, lock);
1189                                         // Checking if a resource exists at this path
1190                                         exists = true;
1191                                         try {
1192                                                 object = getService().getResourceAtPath(user.getId(), path, true);
1193                                         } catch (ObjectNotFoundException e) {
1194                                                 exists = false;
1195                                         } catch (RpcException e) {
1196                                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1197                                                 return;
1198                                         }
1199                                         if (!exists) {
1200                                                 // "Creating" a lock-null resource
1201                                                 int slash = lock.path.lastIndexOf('/');
1202                                                 String parentPath = lock.path.substring(0, slash);
1203                                                 Vector<String> lockNulls = lockNullResources.get(parentPath);
1204                                                 if (lockNulls == null) {
1205                                                         lockNulls = new Vector<String>();
1206                                                         lockNullResources.put(parentPath, lockNulls);
1207                                                 }
1208                                                 lockNulls.addElement(lock.path);
1209                                         }
1210                                         // Add the Lock-Token header as by RFC 2518 8.10.1
1211                                         // - only do this for newly created locks
1212                                         resp.addHeader("Lock-Token", "<opaquelocktoken:" + lockToken + ">");
1213                                 }
1214                         }
1215                 }
1216
1217                 if (lockRequestType == LOCK_REFRESH) {
1218                         String ifHeader = req.getHeader("If");
1219                         if (ifHeader == null)
1220                                 ifHeader = "";
1221                         // Checking resource locks
1222                         LockInfo toRenew = resourceLocks.get(path);
1223                         Enumeration tokenList = null;
1224                         if (lock != null) {
1225                                 // At least one of the tokens of the locks must have been given
1226                                 tokenList = toRenew.tokens.elements();
1227                                 while (tokenList.hasMoreElements()) {
1228                                         String token = (String) tokenList.nextElement();
1229                                         if (ifHeader.indexOf(token) != -1) {
1230                                                 toRenew.expiresAt = lock.expiresAt;
1231                                                 lock = toRenew;
1232                                         }
1233                                 }
1234                         }
1235                         // Checking inheritable collection locks
1236                         Enumeration collectionLocksList = collectionLocks.elements();
1237                         while (collectionLocksList.hasMoreElements()) {
1238                                 toRenew = (LockInfo) collectionLocksList.nextElement();
1239                                 if (path.equals(toRenew.path)) {
1240                                         tokenList = toRenew.tokens.elements();
1241                                         while (tokenList.hasMoreElements()) {
1242                                                 String token = (String) tokenList.nextElement();
1243                                                 if (ifHeader.indexOf(token) != -1) {
1244                                                         toRenew.expiresAt = lock.expiresAt;
1245                                                         lock = toRenew;
1246                                                 }
1247                                         }
1248                                 }
1249                         }
1250                 }
1251                 // Set the status, then generate the XML response containing
1252                 // the lock information.
1253                 XMLWriter generatedXML = new XMLWriter();
1254                 generatedXML.writeXMLHeader();
1255                 generatedXML.writeElement(null, "prop" + generateNamespaceDeclarations(), XMLWriter.OPENING);
1256                 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
1257                 lock.toXML(generatedXML);
1258                 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING);
1259                 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1260
1261                 resp.setStatus(WebdavStatus.SC_OK);
1262                 resp.setContentType("text/xml; charset=UTF-8");
1263                 Writer writer = resp.getWriter();
1264                 writer.write(generatedXML.toString());
1265                 writer.close();
1266         }
1267
1268         /**
1269          * The MOVE method.
1270          *
1271          * @param req the HTTP request
1272          * @param resp the HTTP response
1273          * @throws IOException if an error occurs while sending the response
1274          * @throws ServletException
1275          */
1276         private void doMove(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1277                 if (isLocked(req)) {
1278                         resp.sendError(WebdavStatus.SC_LOCKED);
1279                         return;
1280                 }
1281
1282                 String path = getRelativePath(req);
1283
1284                 if (copyResource(req, resp))
1285                         deleteResource(path, req, resp, false);
1286         }
1287
1288         /**
1289          * The COPY method.
1290          *
1291          * @param req the HTTP request
1292          * @param resp the HTTP response
1293          * @throws IOException if an error occurs while sending the response
1294          * @throws ServletException
1295          */
1296         private void doCopy(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1297                 copyResource(req, resp);
1298         }
1299
1300         /**
1301          * The MKCOL method.
1302          *
1303          * @param req the HTTP request
1304          * @param resp the HTTP response
1305          * @throws IOException if an error occurs while sending the response
1306          * @throws ServletException
1307          */
1308         private void doMkcol(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
1309                 if (isLocked(req)) {
1310                         resp.sendError(WebdavStatus.SC_LOCKED);
1311                         return;
1312                 }
1313                 String path = getRelativePath(req);
1314                 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
1315                         resp.sendError(WebdavStatus.SC_FORBIDDEN);
1316                         return;
1317                 }
1318
1319                 User user = getUser(req);
1320                 boolean exists = true;
1321                 try {
1322                         getService().getResourceAtPath(user.getId(), path, true);
1323                 } catch (ObjectNotFoundException e) {
1324                         exists = false;
1325                 } catch (RpcException e) {
1326                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1327                         return;
1328                 }
1329
1330                 // Can't create a collection if a resource already exists at the given
1331                 // path.
1332                 if (exists) {
1333                         // Get allowed methods.
1334                         StringBuffer methodsAllowed;
1335                         try {
1336                                 methodsAllowed = determineMethodsAllowed(req);
1337                         } catch (RpcException e) {
1338                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1339                                 return;
1340                         }
1341                         resp.addHeader("Allow", methodsAllowed.toString());
1342                         resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
1343                         return;
1344                 }
1345
1346                 if (req.getInputStream().available() > 0) {
1347                         DocumentBuilder documentBuilder = getDocumentBuilder();
1348                         try {
1349                                 Document document = documentBuilder.parse(new InputSource(req.getInputStream()));
1350                                 // TODO : Process this request body
1351                                 resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
1352                                 return;
1353                         } catch (SAXException saxe) {
1354                                 // Parse error - assume invalid content
1355                                 resp.sendError(WebdavStatus.SC_BAD_REQUEST);
1356                                 return;
1357                         }
1358                 }
1359
1360                 Object parent;
1361                 try {
1362                         parent = getService().getResourceAtPath(user.getId(), getParentPath(path), true);
1363                 } catch (ObjectNotFoundException e1) {
1364                         resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1365                         return;
1366                 } catch (RpcException e1) {
1367                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1368                         return;
1369                 }
1370                 try {
1371                         if (parent instanceof FolderDTO) {
1372                                 FolderDTO folder = (FolderDTO) parent;
1373                                 getService().createFolder(user.getId(), folder.getId(), getLastElement(path));
1374                         } else {
1375                                 resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1376                                 return;
1377                         }
1378                 } catch (DuplicateNameException e) {
1379                         // XXX If the existing name is a folder we should be returning
1380                         // SC_METHOD_NOT_ALLOWED, or even better, just do the createFolder
1381                         // without checking first and then deal with the exceptions.
1382                         resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1383                         return;
1384                 } catch (InsufficientPermissionsException e) {
1385                         resp.sendError(WebdavStatus.SC_FORBIDDEN, WebdavStatus.getStatusText(WebdavStatus.SC_FORBIDDEN));
1386                         return;
1387                 } catch (ObjectNotFoundException e) {
1388                         resp.sendError(WebdavStatus.SC_CONFLICT, WebdavStatus.getStatusText(WebdavStatus.SC_CONFLICT));
1389                         return;
1390                 } catch (RpcException e) {
1391                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1392                         return;
1393                 }
1394                 resp.setStatus(WebdavStatus.SC_CREATED);
1395                 // Removing any lock-null resource which would be present
1396                 lockNullResources.remove(path);
1397         }
1398
1399         /**
1400          * For a provided path, remove the last element and return the rest, that is
1401          * the path of the parent folder.
1402          *
1403          * @param path the specified path
1404          * @return the path of the parent folder
1405          * @throws ObjectNotFoundException if the provided string contains no path
1406          *             delimiters
1407          */
1408         protected String getParentPath(String path) throws ObjectNotFoundException {
1409                 int lastDelimiter = path.lastIndexOf('/');
1410                 if (lastDelimiter == 0)
1411                         return "/";
1412                 if (lastDelimiter == -1)
1413                         // No path found.
1414                         throw new ObjectNotFoundException("There is no parent in the path: " + path);
1415                 else if (lastDelimiter < path.length() - 1)
1416                         // Return the part before the delimiter.
1417                         return path.substring(0, lastDelimiter);
1418                 else {
1419                         // Remove the trailing delimiter and then recurse.
1420                         String strippedTrail = path.substring(0, lastDelimiter);
1421                         return getParentPath(strippedTrail);
1422                 }
1423         }
1424
1425         /**
1426          * Get the last element in a path that denotes the file or folder name.
1427          *
1428          * @param path the provided path
1429          * @return the last element in the path
1430          */
1431         protected String getLastElement(String path) {
1432                 int lastDelimiter = path.lastIndexOf('/');
1433                 if (lastDelimiter == -1)
1434                         // No path found.
1435                         return path;
1436                 else if (lastDelimiter < path.length() - 1)
1437                         // Return the part after the delimiter.
1438                         return path.substring(lastDelimiter + 1);
1439                 else {
1440                         // Remove the trailing delimiter and then recurse.
1441                         String strippedTrail = path.substring(0, lastDelimiter);
1442                         return getLastElement(strippedTrail);
1443                 }
1444         }
1445
1446         /**
1447          * Only use the PathInfo for determining the requested path. If the
1448          * ServletPath is non-null, it will be because the WebDAV servlet has been
1449          * mapped to a URL other than /* to configure editing at different URL than
1450          * normal viewing.
1451          *
1452          * @param request the servlet request we are processing
1453          * @return the relative path
1454          */
1455         protected String getRelativePath(HttpServletRequest request) {
1456                 String result = request.getPathInfo();
1457                 if (result == null || result.equals(""))
1458                         result = "/";
1459                 return result;
1460
1461         }
1462
1463         /**
1464          * Return JAXP document builder instance.
1465          *
1466          * @return the DocumentBuilder
1467          * @throws ServletException
1468          */
1469         private DocumentBuilder getDocumentBuilder() throws ServletException {
1470                 DocumentBuilder documentBuilder = null;
1471                 DocumentBuilderFactory documentBuilderFactory = null;
1472                 try {
1473                         documentBuilderFactory = DocumentBuilderFactory.newInstance();
1474                         documentBuilderFactory.setNamespaceAware(true);
1475                         documentBuilderFactory.setExpandEntityReferences(false);
1476                         documentBuilder = documentBuilderFactory.newDocumentBuilder();
1477                         documentBuilder.setEntityResolver(new WebdavResolver(getServletContext()));
1478                 } catch (ParserConfigurationException e) {
1479                         throw new ServletException("Error while creating a document builder");
1480                 }
1481                 return documentBuilder;
1482         }
1483
1484         /**
1485          * Generate the namespace declarations.
1486          *
1487          * @return the namespace declarations
1488          */
1489         private String generateNamespaceDeclarations() {
1490                 return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
1491         }
1492
1493         /**
1494          * Propfind helper method. Dispays the properties of a lock-null resource.
1495          *
1496          * @param req the HTTP request
1497          * @param resources Resources object associated with this context
1498          * @param generatedXML XML response to the Propfind request
1499          * @param path Path of the current resource
1500          * @param type Propfind type
1501          * @param propertiesVector If the propfind type is find properties by name,
1502          *            then this Vector contains those properties
1503          */
1504         private void parseLockNullProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector propertiesVector) {
1505
1506                 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1507                 // (the "toUpperCase()" avoids problems on Windows systems)
1508                 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF"))
1509                         return;
1510
1511                 // Retrieving the lock associated with the lock-null resource
1512                 LockInfo lock = resourceLocks.get(path);
1513
1514                 if (lock == null)
1515                         return;
1516
1517                 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1518                 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1519
1520                 // Generating href element
1521                 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1522
1523                 String absoluteUri = req.getRequestURI();
1524                 String relativePath = getRelativePath(req);
1525                 String toAppend = path.substring(relativePath.length());
1526                 if (!toAppend.startsWith("/"))
1527                         toAppend = "/" + toAppend;
1528
1529                 generatedXML.writeText(rewriteUrl(normalize(absoluteUri + toAppend)));
1530
1531                 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1532
1533                 String resourceName = path;
1534                 int lastSlash = path.lastIndexOf('/');
1535                 if (lastSlash != -1)
1536                         resourceName = resourceName.substring(lastSlash + 1);
1537
1538                 switch (type) {
1539
1540                         case FIND_ALL_PROP:
1541
1542                                 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1543                                 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1544
1545                                 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(lock.creationDate.getTime()));
1546                                 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1547                                 generatedXML.writeData(resourceName);
1548                                 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1549                                 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(lock.creationDate.getTime(), null));
1550                                 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(0));
1551                                 generatedXML.writeProperty(null, "getcontenttype", "");
1552                                 generatedXML.writeProperty(null, "getetag", "");
1553                                 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1554                                 generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
1555                                 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1556
1557                                 generatedXML.writeProperty(null, "source", "");
1558
1559                                 String supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1560                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1561                                 generatedXML.writeText(supportedLocks);
1562                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1563
1564                                 generateLockDiscovery(path, generatedXML);
1565
1566                                 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1567                                 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1568                                 generatedXML.writeText(status);
1569                                 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1570                                 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1571
1572                                 break;
1573
1574                         case FIND_PROPERTY_NAMES:
1575
1576                                 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1577                                 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1578
1579                                 generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT);
1580                                 generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
1581                                 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1582                                 generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT);
1583                                 generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT);
1584                                 generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT);
1585                                 generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT);
1586                                 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1587                                 generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
1588                                 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
1589
1590                                 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1591                                 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1592                                 generatedXML.writeText(status);
1593                                 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1594                                 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1595
1596                                 break;
1597
1598                         case FIND_BY_PROPERTY:
1599
1600                                 Vector<String> propertiesNotFound = new Vector<String>();
1601
1602                                 // Parse the list of properties
1603
1604                                 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1605                                 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1606
1607                                 Enumeration properties = propertiesVector.elements();
1608
1609                                 while (properties.hasMoreElements()) {
1610
1611                                         String property = (String) properties.nextElement();
1612
1613                                         if (property.equals("creationdate"))
1614                                                 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(lock.creationDate.getTime()));
1615                                         else if (property.equals("displayname")) {
1616                                                 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1617                                                 generatedXML.writeData(resourceName);
1618                                                 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1619                                         } else if (property.equals("getcontentlanguage"))
1620                                                 generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1621                                         else if (property.equals("getcontentlength"))
1622                                                 generatedXML.writeProperty(null, "getcontentlength", String.valueOf(0));
1623                                         else if (property.equals("getcontenttype"))
1624                                                 generatedXML.writeProperty(null, "getcontenttype", "");
1625                                         else if (property.equals("getetag"))
1626                                                 generatedXML.writeProperty(null, "getetag", "");
1627                                         else if (property.equals("getlastmodified"))
1628                                                 generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(lock.creationDate.getTime(), null));
1629                                         else if (property.equals("resourcetype")) {
1630                                                 generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1631                                                 generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
1632                                                 generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1633                                         } else if (property.equals("source"))
1634                                                 generatedXML.writeProperty(null, "source", "");
1635                                         else if (property.equals("supportedlock")) {
1636                                                 supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1637                                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1638                                                 generatedXML.writeText(supportedLocks);
1639                                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1640                                         } else if (property.equals("lockdiscovery")) {
1641                                                 if (!generateLockDiscovery(path, generatedXML))
1642                                                         propertiesNotFound.addElement(property);
1643                                         } else
1644                                                 propertiesNotFound.addElement(property);
1645
1646                                 }
1647
1648                                 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1649                                 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1650                                 generatedXML.writeText(status);
1651                                 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1652                                 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1653
1654                                 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1655
1656                                 if (propertiesNotFoundList.hasMoreElements()) {
1657
1658                                         status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
1659
1660                                         generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1661                                         generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1662
1663                                         while (propertiesNotFoundList.hasMoreElements())
1664                                                 generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
1665
1666                                         generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1667                                         generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1668                                         generatedXML.writeText(status);
1669                                         generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1670                                         generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1671
1672                                 }
1673
1674                                 break;
1675
1676                 }
1677
1678                 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1679
1680         }
1681
1682         /**
1683          * Return a context-relative path, beginning with a "/", that represents the
1684          * canonical version of the specified path after ".." and "." elements are
1685          * resolved out. If the specified path attempts to go outside the boundaries
1686          * of the current context (i.e. too many ".." path elements are present),
1687          * return <code>null</code> instead.
1688          *
1689          * @param path Path to be normalized
1690          * @return the normalized path
1691          */
1692         private String normalize(String path) {
1693                 if (path == null)
1694                         return null;
1695
1696                 // Create a place for the normalized path
1697                 String normalized = path;
1698                 if (normalized.equals("/."))
1699                         return "/";
1700
1701                 // Normalize the slashes and add leading slash if necessary
1702                 if (normalized.indexOf('\\') >= 0)
1703                         normalized = normalized.replace('\\', '/');
1704                 if (!normalized.startsWith("/"))
1705                         normalized = "/" + normalized;
1706
1707                 // Resolve occurrences of "//" in the normalized path
1708                 while (true) {
1709                         int index = normalized.indexOf("//");
1710                         if (index < 0)
1711                                 break;
1712                         normalized = normalized.substring(0, index) + normalized.substring(index + 1);
1713                 }
1714
1715                 // Resolve occurrences of "/./" in the normalized path
1716                 while (true) {
1717                         int index = normalized.indexOf("/./");
1718                         if (index < 0)
1719                                 break;
1720                         normalized = normalized.substring(0, index) + normalized.substring(index + 2);
1721                 }
1722
1723                 // Resolve occurrences of "/../" in the normalized path
1724                 while (true) {
1725                         int index = normalized.indexOf("/../");
1726                         if (index < 0)
1727                                 break;
1728                         if (index == 0)
1729                                 return null; // Trying to go outside our context
1730                         int index2 = normalized.lastIndexOf('/', index - 1);
1731                         normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
1732                 }
1733
1734                 // Return the normalized path that we have completed
1735                 return normalized;
1736
1737         }
1738
1739         /**
1740          * Propfind helper method.
1741          *
1742          * @param req The servlet request
1743          * @param resources Resources object associated with this context
1744          * @param generatedXML XML response to the Propfind request
1745          * @param path Path of the current resource
1746          * @param type Propfind type
1747          * @param propertiesVector If the propfind type is find properties by name,
1748          *            then this Vector contains those properties
1749          * @param resource the resource object
1750          */
1751         private void parseProperties(HttpServletRequest req, XMLWriter generatedXML, String path, int type, Vector<String> propertiesVector, Object resource) {
1752
1753                 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
1754                 // (the "toUpperCase()" avoids problems on Windows systems)
1755                 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF"))
1756                         return;
1757
1758                 FolderDTO folder = null;
1759                 FileHeaderDTO file = null;
1760                 if (resource instanceof FolderDTO)
1761                         folder = (FolderDTO) resource;
1762                 else
1763                         file = (FileHeaderDTO) resource;
1764                 // Retrieve the creation date.
1765                 long creation = 0;
1766                 if (folder != null)
1767                         creation = folder.getAuditInfo().getCreationDate().getTime();
1768                 else
1769                         creation = file.getAuditInfo().getCreationDate().getTime();
1770                 // Retrieve the modification date.
1771                 long modification = 0;
1772                 if (folder != null)
1773                         modification = folder.getAuditInfo().getCreationDate().getTime();
1774                 else
1775                         modification = file.getAuditInfo().getCreationDate().getTime();
1776
1777                 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
1778                 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " " + WebdavStatus.getStatusText(WebdavStatus.SC_OK));
1779
1780                 // Generating href element
1781                 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
1782
1783                 String href = req.getContextPath() + req.getServletPath();
1784                 if (href.endsWith("/") && path.startsWith("/"))
1785                         href += path.substring(1);
1786                 else
1787                         href += path;
1788                 if (folder != null && !href.endsWith("/"))
1789                         href += "/";
1790
1791                 generatedXML.writeText(rewriteUrl(href));
1792
1793                 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
1794
1795                 String resourceName = path;
1796                 int lastSlash = path.lastIndexOf('/');
1797                 if (lastSlash != -1)
1798                         resourceName = resourceName.substring(lastSlash + 1);
1799
1800                 switch (type) {
1801
1802                         case FIND_ALL_PROP:
1803
1804                                 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1805                                 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1806
1807                                 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(creation));
1808                                 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1809                                 generatedXML.writeData(resourceName);
1810                                 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1811                                 if (file != null) {
1812                                         generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1813                                         generatedXML.writeProperty(null, "getcontentlength", String.valueOf(file.getFileSize()));
1814                                         // XXX Once we properly store the MIME type in the file,
1815                                         // retrieve it from there.
1816                                         String contentType = getServletContext().getMimeType(file.getName());
1817                                         if (contentType != null)
1818                                                 generatedXML.writeProperty(null, "getcontenttype", contentType);
1819                                         generatedXML.writeProperty(null, "getetag", getETag(file, null));
1820                                         generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1821                                 } else {
1822                                         generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1823                                         generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
1824                                         generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1825                                 }
1826
1827                                 generatedXML.writeProperty(null, "source", "");
1828
1829                                 String supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1830                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1831                                 generatedXML.writeText(supportedLocks);
1832                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1833
1834                                 generateLockDiscovery(path, generatedXML);
1835
1836                                 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1837                                 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1838                                 generatedXML.writeText(status);
1839                                 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1840                                 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1841
1842                                 break;
1843
1844                         case FIND_PROPERTY_NAMES:
1845
1846                                 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1847                                 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1848
1849                                 generatedXML.writeElement(null, "creationdate", XMLWriter.NO_CONTENT);
1850                                 generatedXML.writeElement(null, "displayname", XMLWriter.NO_CONTENT);
1851                                 if (file != null) {
1852                                         generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1853                                         generatedXML.writeElement(null, "getcontentlength", XMLWriter.NO_CONTENT);
1854                                         generatedXML.writeElement(null, "getcontenttype", XMLWriter.NO_CONTENT);
1855                                         generatedXML.writeElement(null, "getetag", XMLWriter.NO_CONTENT);
1856                                         generatedXML.writeElement(null, "getlastmodified", XMLWriter.NO_CONTENT);
1857                                 }
1858                                 generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1859                                 generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
1860                                 generatedXML.writeElement(null, "lockdiscovery", XMLWriter.NO_CONTENT);
1861
1862                                 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1863                                 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1864                                 generatedXML.writeText(status);
1865                                 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1866                                 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1867
1868                                 break;
1869
1870                         case FIND_BY_PROPERTY:
1871
1872                                 Vector<String> propertiesNotFound = new Vector<String>();
1873
1874                                 // Parse the list of properties
1875
1876                                 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1877                                 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1878
1879                                 Enumeration<String> properties = propertiesVector.elements();
1880
1881                                 while (properties.hasMoreElements()) {
1882
1883                                         String property = properties.nextElement();
1884
1885                                         if (property.equals("creationdate"))
1886                                                 generatedXML.writeProperty(null, "creationdate", getISOCreationDate(creation));
1887                                         else if (property.equals("displayname")) {
1888                                                 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
1889                                                 generatedXML.writeData(resourceName);
1890                                                 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
1891                                         } else if (property.equals("getcontentlanguage")) {
1892                                                 if (folder != null)
1893                                                         propertiesNotFound.addElement(property);
1894                                                 else
1895                                                         generatedXML.writeElement(null, "getcontentlanguage", XMLWriter.NO_CONTENT);
1896                                         } else if (property.equals("getcontentlength")) {
1897                                                 if (folder != null)
1898                                                         propertiesNotFound.addElement(property);
1899                                                 else
1900                                                         generatedXML.writeProperty(null, "getcontentlength", String.valueOf(file.getFileSize()));
1901                                         } else if (property.equals("getcontenttype")) {
1902                                                 if (folder != null)
1903                                                         propertiesNotFound.addElement(property);
1904                                                 else
1905                                                         // XXX Once we properly store the MIME type in the
1906                                                         // file,
1907                                                         // retrieve it from there.
1908                                                         generatedXML.writeProperty(null, "getcontenttype", getServletContext().getMimeType(file.getName()));
1909                                         } else if (property.equals("getetag")) {
1910                                                 if (folder != null)
1911                                                         propertiesNotFound.addElement(property);
1912                                                 else
1913                                                         generatedXML.writeProperty(null, "getetag", getETag(file, null));
1914                                         } else if (property.equals("getlastmodified")) {
1915                                                 if (folder != null)
1916                                                         propertiesNotFound.addElement(property);
1917                                                 else
1918                                                         generatedXML.writeProperty(null, "getlastmodified", FastHttpDateFormat.formatDate(modification, null));
1919                                         } else if (property.equals("resourcetype")) {
1920                                                 if (folder != null) {
1921                                                         generatedXML.writeElement(null, "resourcetype", XMLWriter.OPENING);
1922                                                         generatedXML.writeElement(null, "collection", XMLWriter.NO_CONTENT);
1923                                                         generatedXML.writeElement(null, "resourcetype", XMLWriter.CLOSING);
1924                                                 } else
1925                                                         generatedXML.writeElement(null, "resourcetype", XMLWriter.NO_CONTENT);
1926                                         } else if (property.equals("source"))
1927                                                 generatedXML.writeProperty(null, "source", "");
1928                                         else if (property.equals("supportedlock")) {
1929                                                 supportedLocks = "<lockentry>" + "<lockscope><exclusive/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>" + "<lockentry>" + "<lockscope><shared/></lockscope>" + "<locktype><write/></locktype>" + "</lockentry>";
1930                                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.OPENING);
1931                                                 generatedXML.writeText(supportedLocks);
1932                                                 generatedXML.writeElement(null, "supportedlock", XMLWriter.CLOSING);
1933                                         } else if (property.equals("lockdiscovery")) {
1934                                                 if (!generateLockDiscovery(path, generatedXML))
1935                                                         propertiesNotFound.addElement(property);
1936                                         } else
1937                                                 propertiesNotFound.addElement(property);
1938                                 }
1939
1940                                 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1941                                 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1942                                 generatedXML.writeText(status);
1943                                 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1944                                 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1945
1946                                 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
1947
1948                                 if (propertiesNotFoundList.hasMoreElements()) {
1949
1950                                         status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND + " " + WebdavStatus.getStatusText(WebdavStatus.SC_NOT_FOUND));
1951
1952                                         generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
1953                                         generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
1954
1955                                         while (propertiesNotFoundList.hasMoreElements())
1956                                                 generatedXML.writeElement(null, (String) propertiesNotFoundList.nextElement(), XMLWriter.NO_CONTENT);
1957                                         generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1958                                         generatedXML.writeElement(null, "status", XMLWriter.OPENING);
1959                                         generatedXML.writeText(status);
1960                                         generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
1961                                         generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
1962                                 }
1963
1964                                 break;
1965
1966                 }
1967
1968                 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
1969
1970         }
1971
1972         /**
1973          * Get the ETag associated with a file.
1974          *
1975          * @param file the FileHeaderDTO object for this file
1976          * @param oldBody the old version of the file, if requested
1977          * @return a string containing the ETag
1978          */
1979         protected String getETag(FileHeaderDTO file, FileBodyDTO oldBody) {
1980                 if (oldBody == null)
1981                         return "\"" + file.getFileSize() + "-" + file.getAuditInfo().getModificationDate().getTime() + "\"";
1982                 return "\"" + oldBody.getFileSize() + "-" + oldBody.getAuditInfo().getModificationDate().getTime() + "\"";
1983         }
1984
1985         /**
1986          * URL rewriter.
1987          *
1988          * @param path Path which has to be rewritten
1989          * @return the rewritten URL
1990          */
1991         private String rewriteUrl(String path) {
1992                 return urlEncoder.encode(path);
1993         }
1994
1995         /**
1996          * Print the lock discovery information associated with a path.
1997          *
1998          * @param path Path
1999          * @param generatedXML XML data to which the locks info will be appended
2000          * @return true if at least one lock was displayed
2001          */
2002         private boolean generateLockDiscovery(String path, XMLWriter generatedXML) {
2003                 LockInfo resourceLock = resourceLocks.get(path);
2004                 Enumeration collectionLocksList = collectionLocks.elements();
2005                 boolean wroteStart = false;
2006                 if (resourceLock != null) {
2007                         wroteStart = true;
2008                         generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
2009                         resourceLock.toXML(generatedXML);
2010                 }
2011
2012                 while (collectionLocksList.hasMoreElements()) {
2013                         LockInfo currentLock = (LockInfo) collectionLocksList.nextElement();
2014                         if (path.startsWith(currentLock.path)) {
2015                                 if (!wroteStart) {
2016                                         wroteStart = true;
2017                                         generatedXML.writeElement(null, "lockdiscovery", XMLWriter.OPENING);
2018                                 }
2019                                 currentLock.toXML(generatedXML);
2020                         }
2021                 }
2022
2023                 if (wroteStart)
2024                         generatedXML.writeElement(null, "lockdiscovery", XMLWriter.CLOSING);
2025                 else
2026                         return false;
2027
2028                 return true;
2029
2030         }
2031
2032         /**
2033          * Get creation date in ISO format.
2034          *
2035          * @param creationDate
2036          * @return the formatted date
2037          */
2038         private String getISOCreationDate(long creationDate) {
2039                 String dateValue = null;
2040                 synchronized (creationDateFormat) {
2041                         dateValue = creationDateFormat.format(new Date(creationDate));
2042                 }
2043                 StringBuffer creationDateValue = new StringBuffer(dateValue);
2044                 /*
2045                 int offset = Calendar.getInstance().getTimeZone().getRawOffset()
2046                     / 3600000; // FIXME ?
2047                 if (offset < 0) {
2048                     creationDateValue.append("-");
2049                     offset = -offset;
2050                 } else if (offset > 0) {
2051                     creationDateValue.append("+");
2052                 }
2053                 if (offset != 0) {
2054                     if (offset < 10)
2055                         creationDateValue.append("0");
2056                     creationDateValue.append(offset + ":00");
2057                 } else {
2058                     creationDateValue.append("Z");
2059                 }
2060                  */
2061                 return creationDateValue.toString();
2062         }
2063
2064         /**
2065          * Determines the methods normally allowed for the resource.
2066          *
2067          * @param req the HTTP request
2068          * @return a list of the allowed methods
2069          * @throws RpcException if there is an error while communicating with the
2070          *             backend
2071          */
2072         private StringBuffer determineMethodsAllowed(HttpServletRequest req) throws RpcException {
2073                 StringBuffer methodsAllowed = new StringBuffer();
2074                 boolean exists = true;
2075                 Object object = null;
2076                 User user = getUser(req);
2077                 String path = getRelativePath(req);
2078                 if (user == null && "/".equals(path))
2079                         // Special case: OPTIONS request before authentication
2080                         return new StringBuffer("OPTIONS, GET, HEAD, POST, DELETE, TRACE, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, PROPFIND, PUT");
2081                 try {
2082                         object = getService().getResourceAtPath(user.getId(), path, true);
2083                 } catch (ObjectNotFoundException e) {
2084                         exists = false;
2085                 }
2086
2087                 if (!exists) {
2088                         methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
2089                         return methodsAllowed;
2090                 }
2091
2092                 methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
2093                 methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
2094                 methodsAllowed.append(", PROPFIND");
2095
2096                 if (!(object instanceof FolderDTO))
2097                         methodsAllowed.append(", PUT");
2098
2099                 return methodsAllowed;
2100         }
2101
2102         /**
2103          * Check to see if a resource is currently write locked. The method will
2104          * look at the "If" header to make sure the client has given the appropriate
2105          * lock tokens.
2106          *
2107          * @param req the HTTP request
2108          * @return boolean true if the resource is locked (and no appropriate lock
2109          *         token has been found for at least one of the non-shared locks
2110          *         which are present on the resource).
2111          */
2112         private boolean isLocked(HttpServletRequest req) {
2113                 String path = getRelativePath(req);
2114                 String ifHeader = req.getHeader("If");
2115                 if (ifHeader == null)
2116                         ifHeader = "";
2117                 String lockTokenHeader = req.getHeader("Lock-Token");
2118                 if (lockTokenHeader == null)
2119                         lockTokenHeader = "";
2120                 return isLocked(path, ifHeader + lockTokenHeader);
2121         }
2122
2123         /**
2124          * Check to see if a resource is currently write locked.
2125          *
2126          * @param path Path of the resource
2127          * @param ifHeader "If" HTTP header which was included in the request
2128          * @return boolean true if the resource is locked (and no appropriate lock
2129          *         token has been found for at least one of the non-shared locks
2130          *         which are present on the resource).
2131          */
2132         private boolean isLocked(String path, String ifHeader) {
2133                 // Checking resource locks
2134                 LockInfo lock = resourceLocks.get(path);
2135                 Enumeration tokenList = null;
2136                 if (lock != null && lock.hasExpired())
2137                         resourceLocks.remove(path);
2138                 else if (lock != null) {
2139                         // At least one of the tokens of the locks must have been given
2140                         tokenList = lock.tokens.elements();
2141                         boolean tokenMatch = false;
2142                         while (tokenList.hasMoreElements()) {
2143                                 String token = (String) tokenList.nextElement();
2144                                 if (ifHeader.indexOf(token) != -1)
2145                                         tokenMatch = true;
2146                         }
2147                         if (!tokenMatch)
2148                                 return true;
2149                 }
2150                 // Checking inheritable collection locks
2151                 Enumeration collectionLocksList = collectionLocks.elements();
2152                 while (collectionLocksList.hasMoreElements()) {
2153                         lock = (LockInfo) collectionLocksList.nextElement();
2154                         if (lock.hasExpired())
2155                                 collectionLocks.removeElement(lock);
2156                         else if (path.startsWith(lock.path)) {
2157                                 tokenList = lock.tokens.elements();
2158                                 boolean tokenMatch = false;
2159                                 while (tokenList.hasMoreElements()) {
2160                                         String token = (String) tokenList.nextElement();
2161                                         if (ifHeader.indexOf(token) != -1)
2162                                                 tokenMatch = true;
2163                                 }
2164                                 if (!tokenMatch)
2165                                         return true;
2166                         }
2167                 }
2168                 return false;
2169         }
2170
2171         /**
2172          * Parse the content-range header.
2173          *
2174          * @param request The servlet request we are processing
2175          * @param response The servlet response we are creating
2176          * @return Range
2177          * @throws IOException
2178          */
2179         protected Range parseContentRange(HttpServletRequest request, HttpServletResponse response) throws IOException {
2180
2181                 // Retrieving the content-range header (if any is specified
2182                 String rangeHeader = request.getHeader("Content-Range");
2183
2184                 if (rangeHeader == null)
2185                         return null;
2186
2187                 // bytes is the only range unit supported
2188                 if (!rangeHeader.startsWith("bytes")) {
2189                         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2190                         return null;
2191                 }
2192
2193                 rangeHeader = rangeHeader.substring(6).trim();
2194
2195                 int dashPos = rangeHeader.indexOf('-');
2196                 int slashPos = rangeHeader.indexOf('/');
2197
2198                 if (dashPos == -1) {
2199                         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2200                         return null;
2201                 }
2202
2203                 if (slashPos == -1) {
2204                         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2205                         return null;
2206                 }
2207
2208                 Range range = new Range();
2209
2210                 try {
2211                         range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
2212                         range.end = Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
2213                         range.length = Long.parseLong(rangeHeader.substring(slashPos + 1, rangeHeader.length()));
2214                 } catch (NumberFormatException e) {
2215                         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2216                         return null;
2217                 }
2218
2219                 if (!range.validate()) {
2220                         response.sendError(HttpServletResponse.SC_BAD_REQUEST);
2221                         return null;
2222                 }
2223
2224                 return range;
2225
2226         }
2227
2228         /**
2229          * Handle a partial PUT. New content specified in request is appended to
2230          * existing content in oldRevisionContent (if present). This code does not
2231          * support simultaneous partial updates to the same resource.
2232          *
2233          * @param req
2234          * @param range
2235          * @param path
2236          * @return
2237          * @throws IOException
2238          * @throws RpcException
2239          * @throws InsufficientPermissionsException
2240          * @throws ObjectNotFoundException
2241          */
2242         protected File executePartialPut(HttpServletRequest req, Range range, String path) throws IOException, RpcException, ObjectNotFoundException, InsufficientPermissionsException {
2243                 // Append data specified in ranges to existing content for this
2244                 // resource - create a temporary file on the local file system to
2245                 // perform this operation.
2246                 File tempDir = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
2247                 // Convert all '/' characters to '.' in resourcePath
2248                 String convertedResourcePath = path.replace('/', '.');
2249                 File contentFile = new File(tempDir, convertedResourcePath);
2250                 if (contentFile.createNewFile())
2251                         // Clean up contentFile when Tomcat is terminated.
2252                         contentFile.deleteOnExit();
2253
2254                 RandomAccessFile randAccessContentFile = new RandomAccessFile(contentFile, "rw");
2255
2256                 User user = getUser(req);
2257                 User owner = getOwner(req);
2258                 FileHeaderDTO oldResource = null;
2259                 try {
2260                         Object obj = getService().getResourceAtPath(owner.getId(), path, true);
2261                         if (obj instanceof FileHeaderDTO)
2262                                 oldResource = (FileHeaderDTO) obj;
2263                 } catch (ObjectNotFoundException e) {
2264                         // Do nothing.
2265                 }
2266
2267                 // Copy data in oldRevisionContent to contentFile
2268                 if (oldResource != null) {
2269                         InputStream contents = getService().getFileContents(user.getId(), oldResource.getId());
2270                         BufferedInputStream bufOldRevStream = new BufferedInputStream(contents, BUFFER_SIZE);
2271
2272                         int numBytesRead;
2273                         byte[] copyBuffer = new byte[BUFFER_SIZE];
2274                         while ((numBytesRead = bufOldRevStream.read(copyBuffer)) != -1)
2275                                 randAccessContentFile.write(copyBuffer, 0, numBytesRead);
2276
2277                         bufOldRevStream.close();
2278                 }
2279
2280                 randAccessContentFile.setLength(range.length);
2281
2282                 // Append data in request input stream to contentFile
2283                 randAccessContentFile.seek(range.start);
2284                 int numBytesRead;
2285                 byte[] transferBuffer = new byte[BUFFER_SIZE];
2286                 BufferedInputStream requestBufInStream = new BufferedInputStream(req.getInputStream(), BUFFER_SIZE);
2287                 while ((numBytesRead = requestBufInStream.read(transferBuffer)) != -1)
2288                         randAccessContentFile.write(transferBuffer, 0, numBytesRead);
2289                 randAccessContentFile.close();
2290                 requestBufInStream.close();
2291
2292                 return contentFile;
2293
2294         }
2295
2296         /**
2297          * Serve the specified resource, optionally including the data content.
2298          *
2299          * @param req The servlet request we are processing
2300          * @param resp The servlet response we are creating
2301          * @param content Should the content be included?
2302          * @exception IOException if an input/output error occurs
2303          * @exception ServletException if a servlet-specified error occurs
2304          * @throws RpcException
2305          * @throws InsufficientPermissionsException
2306          * @throws ObjectNotFoundException
2307          */
2308         protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException, ServletException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2309
2310                 // Identify the requested resource path
2311                 String path = getRelativePath(req);
2312                 if (logger.isDebugEnabled())
2313                         if (content)
2314                                 logger.debug("Serving resource '" + path + "' headers and data");
2315                         else
2316                                 logger.debug("Serving resource '" + path + "' headers only");
2317
2318                 User user = getUser(req);
2319                 boolean exists = true;
2320                 Object resource = null;
2321                 FileHeaderDTO file = null;
2322                 FolderDTO folder = null;
2323                 try {
2324                         resource = getService().getResourceAtPath(user.getId(), path, true);
2325                 } catch (ObjectNotFoundException e) {
2326                         exists = false;
2327                 } catch (RpcException e) {
2328                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
2329                         return;
2330                 }
2331
2332                 if (!exists) {
2333                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
2334                         return;
2335                 }
2336
2337                 if (resource instanceof FolderDTO)
2338                         folder = (FolderDTO) resource;
2339                 else
2340                         file = (FileHeaderDTO) resource;
2341
2342                 // If the resource is not a collection, and the resource path
2343                 // ends with "/" or "\", return NOT FOUND
2344                 if (folder == null)
2345                         if (path.endsWith("/") || path.endsWith("\\")) {
2346                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
2347                                 return;
2348                         }
2349
2350                 // Check if the conditions specified in the optional If headers are
2351                 // satisfied.
2352                 if (folder == null)
2353                         // Checking If headers
2354                         if (!checkIfHeaders(req, resp, file, null))
2355                                 return;
2356
2357                 // Find content type.
2358                 String contentType = null;
2359                 if (file != null) {
2360                         contentType = file.getMimeType();
2361                         if (contentType == null) {
2362                                 contentType = getServletContext().getMimeType(file.getName());
2363                                 file.setMimeType(contentType);
2364                         }
2365                 } else
2366                         contentType = "text/html;charset=UTF-8";
2367
2368                 ArrayList ranges = null;
2369                 long contentLength = -1L;
2370
2371                 if (file != null) {
2372                         // Parse range specifier
2373                         ranges = parseRange(req, resp, file, null);
2374                         // ETag header
2375                         resp.setHeader("ETag", getETag(file, null));
2376                         // Last-Modified header
2377                         resp.setHeader("Last-Modified", getLastModifiedHttp(file.getAuditInfo()));
2378                         // Get content length
2379                         contentLength = file.getFileSize();
2380                         // Special case for zero length files, which would cause a
2381                         // (silent) ISE when setting the output buffer size
2382                         if (contentLength == 0L)
2383                                 content = false;
2384                 }
2385
2386                 ServletOutputStream ostream = null;
2387                 PrintWriter writer = null;
2388
2389                 if (content)
2390                         try {
2391                                 ostream = resp.getOutputStream();
2392                         } catch (IllegalStateException e) {
2393                                 // If it fails, we try to get a Writer instead if we're
2394                                 // trying to serve a text file
2395                                 if (contentType == null || contentType.startsWith("text") || contentType.endsWith("xml"))
2396                                         writer = resp.getWriter();
2397                                 else
2398                                         throw e;
2399                         }
2400
2401                 if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
2402                         // Set the appropriate output headers
2403                         if (contentType != null) {
2404                                 if (logger.isDebugEnabled())
2405                                         logger.debug("DefaultServlet.serveFile:  contentType='" + contentType + "'");
2406                                 resp.setContentType(contentType);
2407                         }
2408                         if (file != null && contentLength >= 0) {
2409                                 if (logger.isDebugEnabled())
2410                                         logger.debug("DefaultServlet.serveFile:  contentLength=" + contentLength);
2411                                 if (contentLength < Integer.MAX_VALUE)
2412                                         resp.setContentLength((int) contentLength);
2413                                 else
2414                                         // Set the content-length as String to be able to use a long
2415                                         resp.setHeader("content-length", "" + contentLength);
2416                         }
2417
2418                         InputStream renderResult = null;
2419                         if (folder != null)
2420                                 if (content)
2421                                         // Serve the directory browser
2422                                         renderResult = renderHtml(req.getContextPath(), path, folder, req);
2423
2424                         // Copy the input stream to our output stream (if requested)
2425                         if (content) {
2426                                 try {
2427                                         resp.setBufferSize(output);
2428                                 } catch (IllegalStateException e) {
2429                                         // Silent catch
2430                                 }
2431                                 if (ostream != null)
2432                                         copy(file, renderResult, ostream, req, null);
2433                                 else
2434                                         copy(file, renderResult, writer, req, null);
2435                         }
2436                 } else {
2437                         if (ranges == null || ranges.isEmpty())
2438                                 return;
2439                         // Partial content response.
2440                         resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
2441
2442                         if (ranges.size() == 1) {
2443                                 Range range = (Range) ranges.get(0);
2444                                 resp.addHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + range.length);
2445                                 long length = range.end - range.start + 1;
2446                                 if (length < Integer.MAX_VALUE)
2447                                         resp.setContentLength((int) length);
2448                                 else
2449                                         // Set the content-length as String to be able to use a long
2450                                         resp.setHeader("content-length", "" + length);
2451
2452                                 if (contentType != null) {
2453                                         if (logger.isDebugEnabled())
2454                                                 logger.debug("DefaultServlet.serveFile:  contentType='" + contentType + "'");
2455                                         resp.setContentType(contentType);
2456                                 }
2457
2458                                 if (content) {
2459                                         try {
2460                                                 resp.setBufferSize(output);
2461                                         } catch (IllegalStateException e) {
2462                                                 // Silent catch
2463                                         }
2464                                         if (ostream != null)
2465                                                 copy(file, ostream, range, req, null);
2466                                         else
2467                                                 copy(file, writer, range, req, null);
2468                                 }
2469
2470                         } else {
2471
2472                                 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
2473
2474                                 if (content) {
2475                                         try {
2476                                                 resp.setBufferSize(output);
2477                                         } catch (IllegalStateException e) {
2478                                                 // Silent catch
2479                                         }
2480                                         if (ostream != null)
2481                                                 copy(file, ostream, ranges.iterator(), contentType, req, null);
2482                                         else
2483                                                 copy(file, writer, ranges.iterator(), contentType, req, null);
2484                                 }
2485
2486                         }
2487
2488                 }
2489
2490         }
2491
2492         /**
2493          * Retrieve the last modified date of a resource in HTTP format.
2494          *
2495          * @param auditInfo the audit info for the specified resource
2496          * @return the last modified date in HTTP format
2497          */
2498         protected String getLastModifiedHttp(AuditInfoDTO auditInfo) {
2499                 Date modifiedDate = auditInfo.getModificationDate();
2500                 if (modifiedDate == null)
2501                         modifiedDate = auditInfo.getCreationDate();
2502                 if (modifiedDate == null)
2503                         modifiedDate = new Date();
2504                 String lastModifiedHttp = null;
2505                 synchronized (format) {
2506                         lastModifiedHttp = format.format(modifiedDate);
2507                 }
2508                 return lastModifiedHttp;
2509         }
2510
2511         /**
2512          * Parse the range header.
2513          *
2514          * @param request The servlet request we are processing
2515          * @param response The servlet response we are creating
2516          * @param file
2517          * @param oldBody the old version of the file, if requested
2518          * @return Vector of ranges
2519          * @throws IOException
2520          */
2521         protected ArrayList parseRange(HttpServletRequest request, HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2522                 // Checking If-Range
2523                 String headerValue = request.getHeader("If-Range");
2524                 if (headerValue != null) {
2525                         long headerValueTime = -1L;
2526                         try {
2527                                 headerValueTime = request.getDateHeader("If-Range");
2528                         } catch (IllegalArgumentException e) {
2529                                 // Do nothing.
2530                         }
2531
2532                         String eTag = getETag(file, oldBody);
2533                         long lastModified = oldBody == null ?
2534                                                 file.getAuditInfo().getModificationDate().getTime() :
2535                                                 oldBody.getAuditInfo().getModificationDate().getTime();
2536
2537                         if (headerValueTime == -1L) {
2538                                 // If the ETag the client gave does not match the entity
2539                                 // etag, then the entire entity is returned.
2540                                 if (!eTag.equals(headerValue.trim()))
2541                                         return FULL;
2542                         } else
2543                         // If the timestamp of the entity the client got is older than
2544                         // the last modification date of the entity, the entire entity
2545                         // is returned.
2546                         if (lastModified > headerValueTime + 1000)
2547                                 return FULL;
2548                 }
2549
2550                 long fileLength = oldBody == null ? file.getFileSize() : oldBody.getFileSize();
2551                 if (fileLength == 0)
2552                         return null;
2553
2554                 // Retrieving the range header (if any is specified).
2555                 String rangeHeader = request.getHeader("Range");
2556
2557                 if (rangeHeader == null)
2558                         return null;
2559                 // bytes is the only range unit supported (and I don't see the point
2560                 // of adding new ones).
2561                 if (!rangeHeader.startsWith("bytes")) {
2562                         response.addHeader("Content-Range", "bytes */" + fileLength);
2563                         response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2564                         return null;
2565                 }
2566
2567                 rangeHeader = rangeHeader.substring(6);
2568
2569                 // Vector that will contain all the ranges which are successfully
2570                 // parsed.
2571                 ArrayList<Range> result = new ArrayList<Range>();
2572                 StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
2573                 // Parsing the range list
2574                 while (commaTokenizer.hasMoreTokens()) {
2575                         String rangeDefinition = commaTokenizer.nextToken().trim();
2576
2577                         Range currentRange = new Range();
2578                         currentRange.length = fileLength;
2579
2580                         int dashPos = rangeDefinition.indexOf('-');
2581
2582                         if (dashPos == -1) {
2583                                 response.addHeader("Content-Range", "bytes */" + fileLength);
2584                                 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2585                                 return null;
2586                         }
2587
2588                         if (dashPos == 0)
2589                                 try {
2590                                         long offset = Long.parseLong(rangeDefinition);
2591                                         currentRange.start = fileLength + offset;
2592                                         currentRange.end = fileLength - 1;
2593                                 } catch (NumberFormatException e) {
2594                                         response.addHeader("Content-Range", "bytes */" + fileLength);
2595                                         response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2596                                         return null;
2597                                 }
2598                         else
2599                                 try {
2600                                         currentRange.start = Long.parseLong(rangeDefinition.substring(0, dashPos));
2601                                         if (dashPos < rangeDefinition.length() - 1)
2602                                                 currentRange.end = Long.parseLong(rangeDefinition.substring(dashPos + 1, rangeDefinition.length()));
2603                                         else
2604                                                 currentRange.end = fileLength - 1;
2605                                 } catch (NumberFormatException e) {
2606                                         response.addHeader("Content-Range", "bytes */" + fileLength);
2607                                         response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2608                                         return null;
2609                                 }
2610
2611                         if (!currentRange.validate()) {
2612                                 response.addHeader("Content-Range", "bytes */" + fileLength);
2613                                 response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
2614                                 return null;
2615                         }
2616                         result.add(currentRange);
2617                 }
2618                 return result;
2619         }
2620
2621         /**
2622          * Check if the conditions specified in the optional If headers are
2623          * satisfied.
2624          *
2625          * @param request The servlet request we are processing
2626          * @param response The servlet response we are creating
2627          * @param file the file resource against which the checks will be made
2628          * @param oldBody the old version of the file, if requested
2629          * @return boolean true if the resource meets all the specified conditions,
2630          *         and false if any of the conditions is not satisfied, in which
2631          *         case request processing is stopped
2632          * @throws IOException
2633          */
2634         protected boolean checkIfHeaders(HttpServletRequest request, HttpServletResponse response,
2635                                 FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2636                 // TODO : Checking the WebDAV If header
2637                 return checkIfMatch(request, response, file, oldBody) &&
2638                                 checkIfModifiedSince(request, response, file, oldBody) &&
2639                                 checkIfNoneMatch(request, response, file, oldBody) &&
2640                                 checkIfUnmodifiedSince(request, response, file, oldBody);
2641         }
2642
2643         /**
2644          * Check if the if-match condition is satisfied.
2645          *
2646          * @param request The servlet request we are processing
2647          * @param response The servlet response we are creating
2648          * @param file the file object
2649          * @param oldBody the old version of the file, if requested
2650          * @return boolean true if the resource meets the specified condition, and
2651          *         false if the condition is not satisfied, in which case request
2652          *         processing is stopped
2653          * @throws IOException
2654          */
2655         private boolean checkIfMatch(HttpServletRequest request, HttpServletResponse response,
2656                                 FileHeaderDTO file, FileBodyDTO oldBody) throws IOException {
2657                 String eTag = getETag(file, oldBody);
2658                 String headerValue = request.getHeader("If-Match");
2659                 if (headerValue != null)
2660                         if (headerValue.indexOf('*') == -1) {
2661                                 StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2662                                 boolean conditionSatisfied = false;
2663                                 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2664                                         String currentToken = commaTokenizer.nextToken();
2665                                         if (currentToken.trim().equals(eTag))
2666                                                 conditionSatisfied = true;
2667                                 }
2668                                 // If none of the given ETags match, 412 Precodition failed is
2669                                 // sent back.
2670                                 if (!conditionSatisfied) {
2671                                         response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2672                                         return false;
2673                                 }
2674                         }
2675                 return true;
2676         }
2677
2678         /**
2679          * Check if the if-modified-since condition is satisfied.
2680          *
2681          * @param request The servlet request we are processing
2682          * @param response The servlet response we are creating
2683          * @param file the file object
2684          * @param oldBody the old version of the file, if requested
2685          * @return boolean true if the resource meets the specified condition, and
2686          *         false if the condition is not satisfied, in which case request
2687          *         processing is stopped
2688          */
2689         private boolean checkIfModifiedSince(HttpServletRequest request,
2690                                 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody) {
2691                 try {
2692                         long headerValue = request.getDateHeader("If-Modified-Since");
2693                         long lastModified = oldBody == null ?
2694                                                 file.getAuditInfo().getModificationDate().getTime() :
2695                                                 oldBody.getAuditInfo().getModificationDate().getTime();
2696                         if (headerValue != -1)
2697                                 // If an If-None-Match header has been specified, if modified
2698                                 // since
2699                                 // is ignored.
2700                                 if (request.getHeader("If-None-Match") == null && lastModified < headerValue + 1000) {
2701                                         // The entity has not been modified since the date
2702                                         // specified by the client. This is not an error case.
2703                                         response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2704                                         response.setHeader("ETag", getETag(file, oldBody));
2705                                         return false;
2706                                 }
2707                 } catch (IllegalArgumentException illegalArgument) {
2708                         return true;
2709                 }
2710                 return true;
2711         }
2712
2713         /**
2714          * Check if the if-none-match condition is satisfied.
2715          *
2716          * @param request The servlet request we are processing
2717          * @param response The servlet response we are creating
2718          * @param file the file object
2719          * @param oldBody the old version of the file, if requested
2720          * @return boolean true if the resource meets the specified condition, and
2721          *         false if the condition is not satisfied, in which case request
2722          *         processing is stopped
2723          * @throws IOException
2724          */
2725         private boolean checkIfNoneMatch(HttpServletRequest request,
2726                                 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody)
2727                         throws IOException {
2728                 String eTag = getETag(file, oldBody);
2729                 String headerValue = request.getHeader("If-None-Match");
2730                 if (headerValue != null) {
2731                         boolean conditionSatisfied = false;
2732                         if (!headerValue.equals("*")) {
2733                                 StringTokenizer commaTokenizer = new StringTokenizer(headerValue, ",");
2734                                 while (!conditionSatisfied && commaTokenizer.hasMoreTokens()) {
2735                                         String currentToken = commaTokenizer.nextToken();
2736                                         if (currentToken.trim().equals(eTag))
2737                                                 conditionSatisfied = true;
2738                                 }
2739                         } else
2740                                 conditionSatisfied = true;
2741                         if (conditionSatisfied) {
2742                                 // For GET and HEAD, we should respond with 304 Not Modified.
2743                                 // For every other method, 412 Precondition Failed is sent
2744                                 // back.
2745                                 if ("GET".equals(request.getMethod()) || "HEAD".equals(request.getMethod())) {
2746                                         response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
2747                                         response.setHeader("ETag", getETag(file, oldBody));
2748                                         return false;
2749                                 }
2750                                 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2751                                 return false;
2752                         }
2753                 }
2754                 return true;
2755         }
2756
2757         /**
2758          * Check if the if-unmodified-since condition is satisfied.
2759          *
2760          * @param request The servlet request we are processing
2761          * @param response The servlet response we are creating
2762          * @param file the file object
2763          * @param oldBody the old version of the file, if requested
2764          * @return boolean true if the resource meets the specified condition, and
2765          *         false if the condition is not satisfied, in which case request
2766          *         processing is stopped
2767          * @throws IOException
2768          */
2769         private boolean checkIfUnmodifiedSince(HttpServletRequest request,
2770                                 HttpServletResponse response, FileHeaderDTO file, FileBodyDTO oldBody)
2771                         throws IOException {
2772                 try {
2773                         long lastModified = oldBody == null ?
2774                                                 file.getAuditInfo().getModificationDate().getTime() :
2775                                                 oldBody.getAuditInfo().getModificationDate().getTime();
2776                         long headerValue = request.getDateHeader("If-Unmodified-Since");
2777                         if (headerValue != -1)
2778                                 if (lastModified >= headerValue + 1000) {
2779                                         // The entity has not been modified since the date
2780                                         // specified by the client. This is not an error case.
2781                                         response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
2782                                         return false;
2783                                 }
2784                 } catch (IllegalArgumentException illegalArgument) {
2785                         return true;
2786                 }
2787                 return true;
2788         }
2789
2790         /**
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).
2794          *
2795          * @param file the file resource
2796          * @param is
2797          * @param ostream The output stream to write to
2798          * @param req the HTTP request
2799          * @param oldBody the old version of the file, if requested
2800          * @exception IOException if an input/output error occurs
2801          * @throws RpcException
2802          * @throws InsufficientPermissionsException
2803          * @throws ObjectNotFoundException
2804          */
2805         protected void copy(FileHeaderDTO file, InputStream is, ServletOutputStream ostream,
2806                                 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
2807                                 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2808                 IOException exception = null;
2809                 InputStream resourceInputStream = null;
2810                 User user = getUser(req);
2811                 // Files open for all will not have specified a calling user in the request.
2812                 if (user == null)
2813                         user = getOwner(req);
2814                 if (user == null)
2815                         throw new ObjectNotFoundException("No user or owner specified");
2816                 if (file != null)
2817                         resourceInputStream = oldBody == null ?
2818                                                 getService().getFileContents(user.getId(), file.getId()) :
2819                                                 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2820                 else
2821                         resourceInputStream = is;
2822
2823                 InputStream istream = new BufferedInputStream(resourceInputStream, input);
2824                 // Copy the input stream to the output stream
2825                 exception = copyRange(istream, ostream);
2826                 // Clean up the input stream
2827                 istream.close();
2828                 // Rethrow any exception that has occurred
2829                 if (exception != null)
2830                         throw exception;
2831         }
2832
2833         /**
2834          * Copy the contents of the specified input stream to the specified output
2835          * stream, and ensure that both streams are closed before returning (even in
2836          * the face of an exception).
2837          *
2838          * @param istream The input stream to read from
2839          * @param ostream The output stream to write to
2840          * @return Exception which occurred during processing
2841          */
2842         private IOException copyRange(InputStream istream, ServletOutputStream ostream) {
2843                 // Copy the input stream to the output stream
2844                 IOException exception = null;
2845                 byte buffer[] = new byte[input];
2846                 int len = buffer.length;
2847                 while (true)
2848                         try {
2849                                 len = istream.read(buffer);
2850                                 if (len == -1)
2851                                         break;
2852                                 ostream.write(buffer, 0, len);
2853                         } catch (IOException e) {
2854                                 exception = e;
2855                                 len = -1;
2856                                 break;
2857                         }
2858                 return exception;
2859         }
2860
2861         /**
2862          * Copy the contents of the specified input stream to the specified output
2863          * stream, and ensure that both streams are closed before returning (even in
2864          * the face of an exception).
2865          *
2866          * @param file
2867          * @param is
2868          * @param resourceInfo The resource info
2869          * @param writer The writer to write to
2870          * @param req the HTTP request
2871          * @param oldBody the old version of the file, if requested
2872          * @exception IOException if an input/output error occurs
2873          * @throws RpcException
2874          * @throws InsufficientPermissionsException
2875          * @throws ObjectNotFoundException
2876          */
2877         protected void copy(FileHeaderDTO file, InputStream is, PrintWriter writer,
2878                                 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
2879                                 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2880                 IOException exception = null;
2881
2882                 User user = getUser(req);
2883                 InputStream resourceInputStream = null;
2884                 if (file != null)
2885                         resourceInputStream = oldBody == null ?
2886                                                 getService().getFileContents(user.getId(), file.getId()) :
2887                                                 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2888                 else
2889                         resourceInputStream = is;
2890
2891                 Reader reader;
2892                 if (fileEncoding == null)
2893                         reader = new InputStreamReader(resourceInputStream);
2894                 else
2895                         reader = new InputStreamReader(resourceInputStream, fileEncoding);
2896
2897                 // Copy the input stream to the output stream
2898                 exception = copyRange(reader, writer);
2899                 // Clean up the reader
2900                 reader.close();
2901                 // Rethrow any exception that has occurred
2902                 if (exception != null)
2903                         throw exception;
2904         }
2905
2906         /**
2907          * Copy the contents of the specified input stream to the specified output
2908          * stream, and ensure that both streams are closed before returning (even in
2909          * the face of an exception).
2910          *
2911          * @param reader The reader to read from
2912          * @param writer The writer to write to
2913          * @return Exception which occurred during processing
2914          */
2915         private IOException copyRange(Reader reader, PrintWriter writer) {
2916                 // Copy the input stream to the output stream
2917                 IOException exception = null;
2918                 char buffer[] = new char[input];
2919                 int len = buffer.length;
2920                 while (true)
2921                         try {
2922                                 len = reader.read(buffer);
2923                                 if (len == -1)
2924                                         break;
2925                                 writer.write(buffer, 0, len);
2926                         } catch (IOException e) {
2927                                 exception = e;
2928                                 len = -1;
2929                                 break;
2930                         }
2931                 return exception;
2932         }
2933
2934         /**
2935          * Copy the contents of the specified input stream to the specified output
2936          * stream, and ensure that both streams are closed before returning (even in
2937          * the face of an exception).
2938          *
2939          * @param file
2940          * @param writer The writer to write to
2941          * @param ranges Enumeration of the ranges the client wanted to retrieve
2942          * @param contentType Content type of the resource
2943          * @param req the HTTP request
2944          * @param oldBody the old version of the file, if requested
2945          * @exception IOException if an input/output error occurs
2946          * @throws RpcException
2947          * @throws InsufficientPermissionsException
2948          * @throws ObjectNotFoundException
2949          */
2950         protected void copy(FileHeaderDTO file, PrintWriter writer, Iterator ranges,
2951                                 String contentType, HttpServletRequest req, FileBodyDTO oldBody)
2952                         throws IOException, ObjectNotFoundException, InsufficientPermissionsException, RpcException {
2953                 User user = getUser(req);
2954                 IOException exception = null;
2955                 while (exception == null && ranges.hasNext()) {
2956                         InputStream resourceInputStream = oldBody == null ?
2957                                                 getService().getFileContents(user.getId(), file.getId()) :
2958                                                 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
2959                         Reader reader;
2960                         if (fileEncoding == null)
2961                                 reader = new InputStreamReader(resourceInputStream);
2962                         else
2963                                 reader = new InputStreamReader(resourceInputStream, fileEncoding);
2964                         Range currentRange = (Range) ranges.next();
2965                         // Writing MIME header.
2966                         writer.println();
2967                         writer.println("--" + mimeSeparation);
2968                         if (contentType != null)
2969                                 writer.println("Content-Type: " + contentType);
2970                         writer.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
2971                         writer.println();
2972                         // Printing content
2973                         exception = copyRange(reader, writer, currentRange.start, currentRange.end);
2974                         reader.close();
2975                 }
2976                 writer.println();
2977                 writer.print("--" + mimeSeparation + "--");
2978                 // Rethrow any exception that has occurred
2979                 if (exception != null)
2980                         throw exception;
2981         }
2982
2983         /**
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).
2987          *
2988          * @param istream The input stream to read from
2989          * @param ostream The output stream to write to
2990          * @param start Start of the range which will be copied
2991          * @param end End of the range which will be copied
2992          * @return Exception which occurred during processing
2993          */
2994         private IOException copyRange(InputStream istream, ServletOutputStream ostream, long start, long end) {
2995                 if (logger.isDebugEnabled())
2996                         logger.debug("Serving bytes:" + start + "-" + end);
2997                 try {
2998                         istream.skip(start);
2999                 } catch (IOException e) {
3000                         return e;
3001                 }
3002                 IOException exception = null;
3003                 long bytesToRead = end - start + 1;
3004                 byte buffer[] = new byte[input];
3005                 int len = buffer.length;
3006                 while (bytesToRead > 0 && len >= buffer.length) {
3007                         try {
3008                                 len = istream.read(buffer);
3009                                 if (bytesToRead >= len) {
3010                                         ostream.write(buffer, 0, len);
3011                                         bytesToRead -= len;
3012                                 } else {
3013                                         ostream.write(buffer, 0, (int) bytesToRead);
3014                                         bytesToRead = 0;
3015                                 }
3016                         } catch (IOException e) {
3017                                 exception = e;
3018                                 len = -1;
3019                         }
3020                         if (len < buffer.length)
3021                                 break;
3022                 }
3023                 return exception;
3024         }
3025
3026         /**
3027          * Copy the contents of the specified input stream to the specified output
3028          * stream, and ensure that both streams are closed before returning (even in
3029          * the face of an exception).
3030          *
3031          * @param reader The reader to read from
3032          * @param writer The writer to write to
3033          * @param start Start of the range which will be copied
3034          * @param end End of the range which will be copied
3035          * @return Exception which occurred during processing
3036          */
3037         private IOException copyRange(Reader reader, PrintWriter writer, long start, long end) {
3038                 try {
3039                         reader.skip(start);
3040                 } catch (IOException e) {
3041                         return e;
3042                 }
3043                 IOException exception = null;
3044                 long bytesToRead = end - start + 1;
3045                 char buffer[] = new char[input];
3046                 int len = buffer.length;
3047                 while (bytesToRead > 0 && len >= buffer.length) {
3048                         try {
3049                                 len = reader.read(buffer);
3050                                 if (bytesToRead >= len) {
3051                                         writer.write(buffer, 0, len);
3052                                         bytesToRead -= len;
3053                                 } else {
3054                                         writer.write(buffer, 0, (int) bytesToRead);
3055                                         bytesToRead = 0;
3056                                 }
3057                         } catch (IOException e) {
3058                                 exception = e;
3059                                 len = -1;
3060                         }
3061                         if (len < buffer.length)
3062                                 break;
3063                 }
3064                 return exception;
3065         }
3066
3067         /**
3068          * Copy the contents of the specified input stream to the specified output
3069          * stream, and ensure that both streams are closed before returning (even in
3070          * the face of an exception).
3071          *
3072          * @param file
3073          * @param ostream The output stream to write to
3074          * @param range Range the client wanted to retrieve
3075          * @param req the HTTP request
3076          * @param oldBody the old version of the file, if requested
3077          * @exception IOException if an input/output error occurs
3078          * @throws RpcException
3079          * @throws InsufficientPermissionsException
3080          * @throws ObjectNotFoundException
3081          */
3082         protected void copy(FileHeaderDTO file, ServletOutputStream ostream, Range range,
3083                                 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
3084                                 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
3085                 IOException exception = null;
3086                 User user = getUser(req);
3087                 InputStream resourceInputStream = oldBody == null ?
3088                                         getService().getFileContents(user.getId(), file.getId()) :
3089                                         getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3090                 InputStream istream = new BufferedInputStream(resourceInputStream, input);
3091                 exception = copyRange(istream, ostream, range.start, range.end);
3092                 // Clean up the input stream
3093                 istream.close();
3094                 // Rethrow any exception that has occurred
3095                 if (exception != null)
3096                         throw exception;
3097         }
3098
3099         /**
3100          * Copy the contents of the specified input stream to the specified output
3101          * stream, and ensure that both streams are closed before returning (even in
3102          * the face of an exception).
3103          *
3104          * @param file
3105          * @param writer The writer to write to
3106          * @param range Range the client wanted to retrieve
3107          * @param req the HTTP request
3108          * @param oldBody the old version of the file, if requested
3109          * @exception IOException if an input/output error occurs
3110          * @throws RpcException
3111          * @throws InsufficientPermissionsException
3112          * @throws ObjectNotFoundException
3113          */
3114         protected void copy(FileHeaderDTO file, PrintWriter writer, Range range,
3115                                 HttpServletRequest req, FileBodyDTO oldBody) throws IOException,
3116                                 ObjectNotFoundException, InsufficientPermissionsException, RpcException {
3117                 IOException exception = null;
3118                 User user = getUser(req);
3119                 InputStream resourceInputStream = oldBody == null ?
3120                                         getService().getFileContents(user.getId(), file.getId()) :
3121                                         getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3122                 Reader reader;
3123                 if (fileEncoding == null)
3124                         reader = new InputStreamReader(resourceInputStream);
3125                 else
3126                         reader = new InputStreamReader(resourceInputStream, fileEncoding);
3127
3128                 exception = copyRange(reader, writer, range.start, range.end);
3129                 // Clean up the input stream
3130                 reader.close();
3131                 // Rethrow any exception that has occurred
3132                 if (exception != null)
3133                         throw exception;
3134         }
3135
3136         /**
3137          * Copy the contents of the specified input stream to the specified output
3138          * stream, and ensure that both streams are closed before returning (even in
3139          * the face of an exception).
3140          *
3141          * @param file
3142          * @param ostream The output stream to write to
3143          * @param ranges Enumeration of the ranges the client wanted to retrieve
3144          * @param contentType Content type of the resource
3145          * @param req the HTTP request
3146          * @param oldBody the old version of the file, if requested
3147          * @exception IOException if an input/output error occurs
3148          * @throws RpcException
3149          * @throws InsufficientPermissionsException
3150          * @throws ObjectNotFoundException
3151          */
3152         protected void copy(FileHeaderDTO file, ServletOutputStream ostream,
3153                                 Iterator ranges, String contentType, HttpServletRequest req,
3154                                 FileBodyDTO oldBody) throws IOException, ObjectNotFoundException,
3155                                 InsufficientPermissionsException, RpcException {
3156                 IOException exception = null;
3157                 User user = getUser(req);
3158                 while (exception == null && ranges.hasNext()) {
3159                         InputStream resourceInputStream = oldBody == null ?
3160                                                 getService().getFileContents(user.getId(), file.getId()) :
3161                                                 getService().getFileContents(user.getId(), file.getId(), oldBody.getId());
3162                         InputStream istream = new BufferedInputStream(resourceInputStream, input);
3163                         Range currentRange = (Range) ranges.next();
3164                         // Writing MIME header.
3165                         ostream.println();
3166                         ostream.println("--" + mimeSeparation);
3167                         if (contentType != null)
3168                                 ostream.println("Content-Type: " + contentType);
3169                         ostream.println("Content-Range: bytes " + currentRange.start + "-" + currentRange.end + "/" + currentRange.length);
3170                         ostream.println();
3171
3172                         // Printing content
3173                         exception = copyRange(istream, ostream, currentRange.start, currentRange.end);
3174                         istream.close();
3175                 }
3176
3177                 ostream.println();
3178                 ostream.print("--" + mimeSeparation + "--");
3179                 // Rethrow any exception that has occurred
3180                 if (exception != null)
3181                         throw exception;
3182         }
3183
3184         /**
3185          * Return an InputStream to an HTML representation of the contents of this
3186          * directory.
3187          *
3188          * @param contextPath Context path to which our internal paths are relative
3189          * @param path the requested path to the resource
3190          * @param folder the specified directory
3191          * @param req the HTTP request
3192          * @return an input stream with the rendered contents
3193          * @throws IOException
3194          * @throws ServletException
3195          */
3196         private InputStream renderHtml(String contextPath, String path, FolderDTO folder, HttpServletRequest req) throws IOException, ServletException {
3197                 String name = folder.getName();
3198                 // Prepare a writer to a buffered area
3199                 ByteArrayOutputStream stream = new ByteArrayOutputStream();
3200                 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
3201                 PrintWriter writer = new PrintWriter(osWriter);
3202                 StringBuffer sb = new StringBuffer();
3203                 // rewriteUrl(contextPath) is expensive. cache result for later reuse
3204                 String rewrittenContextPath = rewriteUrl(contextPath);
3205                 // Render the page header
3206                 sb.append("<html>\r\n");
3207                 sb.append("<head>\r\n");
3208                 sb.append("<title>");
3209                 sb.append("Index of " + name);
3210                 sb.append("</title>\r\n");
3211                 sb.append("<STYLE><!--");
3212                 sb.append(GSS_CSS);
3213                 sb.append("--></STYLE> ");
3214                 sb.append("</head>\r\n");
3215                 sb.append("<body>");
3216                 sb.append("<h1>");
3217                 sb.append("Index of " + name);
3218
3219                 // Render the link to our parent (if required)
3220                 String parentDirectory = path;
3221                 if (parentDirectory.endsWith("/"))
3222                         parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
3223                 int slash = parentDirectory.lastIndexOf('/');
3224                 if (slash >= 0) {
3225                         String parent = path.substring(0, slash);
3226                         sb.append(" - <a href=\"");
3227                         sb.append(rewrittenContextPath);
3228                         if (parent.equals(""))
3229                                 parent = "/";
3230                         sb.append(rewriteUrl(parent));
3231                         if (!parent.endsWith("/"))
3232                                 sb.append("/");
3233                         sb.append("\">");
3234                         sb.append("<b>");
3235                         sb.append("Up To " + parent);
3236                         sb.append("</b>");
3237                         sb.append("</a>");
3238                 }
3239
3240                 sb.append("</h1>");
3241                 sb.append("<HR size=\"1\" noshade=\"noshade\">");
3242
3243                 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
3244
3245                 // Render the column headings
3246                 sb.append("<tr>\r\n");
3247                 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
3248                 sb.append("Name");
3249                 sb.append("</strong></font></td>\r\n");
3250                 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
3251                 sb.append("Size");
3252                 sb.append("</strong></font></td>\r\n");
3253                 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
3254                 sb.append("Last modified");
3255                 sb.append("</strong></font></td>\r\n");
3256                 sb.append("</tr>");
3257                 // Render the directory entries within this directory
3258                 boolean shade = false;
3259                 Iterator iter = folder.getSubfolders().iterator();
3260                 while (iter.hasNext()) {
3261                         FolderDTO subf = (FolderDTO) iter.next();
3262                         String resourceName = subf.getName();
3263                         if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
3264                                 continue;
3265
3266                         sb.append("<tr");
3267                         if (shade)
3268                                 sb.append(" bgcolor=\"#eeeeee\"");
3269                         sb.append(">\r\n");
3270                         shade = !shade;
3271
3272                         sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
3273                         sb.append("<a href=\"");
3274                         sb.append(rewrittenContextPath);
3275                         sb.append(rewriteUrl(path + resourceName));
3276                         sb.append("/");
3277                         sb.append("\"><tt>");
3278                         sb.append(RequestUtil.filter(resourceName));
3279                         sb.append("/");
3280                         sb.append("</tt></a></td>\r\n");
3281
3282                         sb.append("<td align=\"right\"><tt>");
3283                         sb.append("&nbsp;");
3284                         sb.append("</tt></td>\r\n");
3285
3286                         sb.append("<td align=\"right\"><tt>");
3287                         sb.append(getLastModifiedHttp(folder.getAuditInfo()));
3288                         sb.append("</tt></td>\r\n");
3289
3290                         sb.append("</tr>\r\n");
3291                 }
3292                 List<FileHeaderDTO> files;
3293                 try {
3294                         User user = getUser(req);
3295                         files = getService().getFiles(user.getId(), folder.getId(), true);
3296                 } catch (ObjectNotFoundException e) {
3297                         throw new ServletException(e.getMessage());
3298                 } catch (InsufficientPermissionsException e) {
3299                         throw new ServletException(e.getMessage());
3300                 } catch (RpcException e) {
3301                         throw new ServletException(e.getMessage());
3302                 }
3303                 for (FileHeaderDTO file : files) {
3304                         String resourceName = file.getName();
3305                         if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
3306                                 continue;
3307
3308                         sb.append("<tr");
3309                         if (shade)
3310                                 sb.append(" bgcolor=\"#eeeeee\"");
3311                         sb.append(">\r\n");
3312                         shade = !shade;
3313
3314                         sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
3315                         sb.append("<a href=\"");
3316                         sb.append(rewrittenContextPath);
3317                         sb.append(rewriteUrl(path + resourceName));
3318                         sb.append("\"><tt>");
3319                         sb.append(RequestUtil.filter(resourceName));
3320                         sb.append("</tt></a></td>\r\n");
3321
3322                         sb.append("<td align=\"right\"><tt>");
3323                         sb.append(renderSize(file.getFileSize()));
3324                         sb.append("</tt></td>\r\n");
3325
3326                         sb.append("<td align=\"right\"><tt>");
3327                         sb.append(getLastModifiedHttp(file.getAuditInfo()));
3328                         sb.append("</tt></td>\r\n");
3329
3330                         sb.append("</tr>\r\n");
3331                 }
3332
3333                 // Render the page footer
3334                 sb.append("</table>\r\n");
3335
3336                 sb.append("<HR size=\"1\" noshade=\"noshade\">");
3337
3338                 sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
3339                 sb.append("</body>\r\n");
3340                 sb.append("</html>\r\n");
3341
3342                 // Return an input stream to the underlying bytes
3343                 writer.write(sb.toString());
3344                 writer.flush();
3345                 return new ByteArrayInputStream(stream.toByteArray());
3346
3347         }
3348
3349         /**
3350          * Render the specified file size (in bytes).
3351          *
3352          * @param size File size (in bytes)
3353          * @return the size as a string
3354          */
3355         private String renderSize(long size) {
3356                 long leftSide = size / 1024;
3357                 long rightSide = size % 1024 / 103; // Makes 1 digit
3358                 if (leftSide == 0 && rightSide == 0 && size > 0)
3359                         rightSide = 1;
3360                 return "" + leftSide + "." + rightSide + " kb";
3361         }
3362
3363         /**
3364          * Copy a resource.
3365          *
3366          * @param req Servlet request
3367          * @param resp Servlet response
3368          * @return boolean true if the copy is successful
3369          * @throws IOException
3370          */
3371         private boolean copyResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
3372                 // Parsing destination header
3373                 String destinationPath = req.getHeader("Destination");
3374                 if (destinationPath == null) {
3375                         resp.sendError(WebdavStatus.SC_BAD_REQUEST);
3376                         return false;
3377                 }
3378
3379                 // Remove url encoding from destination
3380                 destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
3381
3382                 int protocolIndex = destinationPath.indexOf("://");
3383                 if (protocolIndex >= 0) {
3384                         // if the Destination URL contains the protocol, we can safely
3385                         // trim everything upto the first "/" character after "://"
3386                         int firstSeparator = destinationPath.indexOf("/", protocolIndex + 4);
3387                         if (firstSeparator < 0)
3388                                 destinationPath = "/";
3389                         else
3390                                 destinationPath = destinationPath.substring(firstSeparator);
3391                 } else {
3392                         String hostName = req.getServerName();
3393                         if (hostName != null && destinationPath.startsWith(hostName))
3394                                 destinationPath = destinationPath.substring(hostName.length());
3395
3396                         int portIndex = destinationPath.indexOf(":");
3397                         if (portIndex >= 0)
3398                                 destinationPath = destinationPath.substring(portIndex);
3399
3400                         if (destinationPath.startsWith(":")) {
3401                                 int firstSeparator = destinationPath.indexOf("/");
3402                                 if (firstSeparator < 0)
3403                                         destinationPath = "/";
3404                                 else
3405                                         destinationPath = destinationPath.substring(firstSeparator);
3406                         }
3407                 }
3408
3409                 // Normalise destination path (remove '.' and '..')
3410                 destinationPath = normalize(destinationPath);
3411
3412                 String contextPath = req.getContextPath();
3413                 if (contextPath != null && destinationPath.startsWith(contextPath))
3414                         destinationPath = destinationPath.substring(contextPath.length());
3415
3416                 String pathInfo = req.getPathInfo();
3417                 if (pathInfo != null) {
3418                         String servletPath = req.getServletPath();
3419                         if (servletPath != null && destinationPath.startsWith(servletPath))
3420                                 destinationPath = destinationPath.substring(servletPath.length());
3421                 }
3422
3423                 if (logger.isDebugEnabled())
3424                         logger.debug("Dest path :" + destinationPath);
3425
3426                 if (destinationPath.toUpperCase().startsWith("/WEB-INF") || destinationPath.toUpperCase().startsWith("/META-INF")) {
3427                         resp.sendError(WebdavStatus.SC_FORBIDDEN);
3428                         return false;
3429                 }
3430
3431                 String path = getRelativePath(req);
3432
3433                 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3434                         resp.sendError(WebdavStatus.SC_FORBIDDEN);
3435                         return false;
3436                 }
3437
3438                 if (destinationPath.equals(path)) {
3439                         resp.sendError(WebdavStatus.SC_FORBIDDEN);
3440                         return false;
3441                 }
3442
3443                 // Parsing overwrite header
3444                 boolean overwrite = true;
3445                 String overwriteHeader = req.getHeader("Overwrite");
3446
3447                 if (overwriteHeader != null)
3448                         if (overwriteHeader.equalsIgnoreCase("T"))
3449                                 overwrite = true;
3450                         else
3451                                 overwrite = false;
3452
3453                 User user = getUser(req);
3454                 // Overwriting the destination
3455                 boolean exists = true;
3456                 try {
3457                         getService().getResourceAtPath(user.getId(), destinationPath, true);
3458                 } catch (ObjectNotFoundException e) {
3459                         exists = false;
3460                 } catch (RpcException e) {
3461                         resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
3462                         return false;
3463                 }
3464
3465                 if (overwrite) {
3466                         // Delete destination resource, if it exists
3467                         if (exists) {
3468                                 if (!deleteResource(destinationPath, req, resp, true))
3469                                         return false;
3470                         } else
3471                                 resp.setStatus(WebdavStatus.SC_CREATED);
3472                 } else // If the destination exists, then it's a conflict
3473                 if (exists) {
3474                         resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
3475                         return false;
3476                 } else
3477                         resp.setStatus(WebdavStatus.SC_CREATED);
3478
3479                 // Copying source to destination.
3480                 Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3481                 boolean result;
3482                 try {
3483                         result = copyResource(errorList, path, destinationPath, req);
3484                 } catch (RpcException e) {
3485                         resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR);
3486                         return false;
3487                 }
3488                 if (!result || !errorList.isEmpty()) {
3489                         sendReport(req, resp, errorList);
3490                         return false;
3491                 }
3492                 // Removing any lock-null resource which would be present at
3493                 // the destination path.
3494                 lockNullResources.remove(destinationPath);
3495                 return true;
3496         }
3497
3498         /**
3499          * Copy a collection.
3500          *
3501          * @param errorList Hashtable containing the list of errors which occurred
3502          *            during the copy operation
3503          * @param source Path of the resource to be copied
3504          * @param theDest Destination path
3505          * @param req the HTTP request
3506          * @return boolean true if the copy is successful
3507          * @throws RpcException
3508          */
3509         private boolean copyResource(Hashtable<String, Integer> errorList, String source, String theDest, HttpServletRequest req) throws RpcException {
3510
3511                 String dest = theDest;
3512                 // Fix the destination path when copying collections.
3513                 if (source.endsWith("/") && !dest.endsWith("/"))
3514                         dest += "/";
3515
3516                 if (logger.isDebugEnabled())
3517                         logger.debug("Copy: " + source + " To: " + dest);
3518
3519                 User user = getUser(req);
3520                 Object object = null;
3521                 try {
3522                         object = getService().getResourceAtPath(user.getId(), source, true);
3523                 } catch (ObjectNotFoundException e) {
3524                 }
3525
3526                 if (object instanceof FolderDTO) {
3527                         FolderDTO folder = (FolderDTO) object;
3528                         try {
3529                                 getService().copyFolder(user.getId(), folder.getId(), dest);
3530                         } catch (ObjectNotFoundException e) {
3531                                 errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3532                                 return false;
3533                         } catch (DuplicateNameException e) {
3534                                 errorList.put(dest, new Integer(WebdavStatus.SC_CONFLICT));
3535                                 return false;
3536                         } catch (InsufficientPermissionsException e) {
3537                                 errorList.put(dest, new Integer(WebdavStatus.SC_FORBIDDEN));
3538                                 return false;
3539                         }
3540
3541                         try {
3542                                 String newSource = source;
3543                                 if (!source.endsWith("/"))
3544                                         newSource += "/";
3545                                 String newDest = dest;
3546                                 if (!dest.endsWith("/"))
3547                                         newDest += "/";
3548                                 // Recursively copy the subfolders.
3549                                 Iterator iter = folder.getSubfolders().iterator();
3550                                 while (iter.hasNext()) {
3551                                         FolderDTO subf = (FolderDTO) iter.next();
3552                                         String resourceName = subf.getName();
3553                                         copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3554                                 }
3555                                 // Recursively copy the files.
3556                                 List<FileHeaderDTO> files;
3557                                 files = getService().getFiles(user.getId(), folder.getId(), true);
3558                                 for (FileHeaderDTO file : files) {
3559                                         String resourceName = file.getName();
3560                                         copyResource(errorList, newSource + resourceName, newDest + resourceName, req);
3561                                 }
3562                         } catch (RpcException e) {
3563                                 errorList.put(dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3564                                 return false;
3565                         } catch (ObjectNotFoundException e) {
3566                                 errorList.put(source, new Integer(WebdavStatus.SC_NOT_FOUND));
3567                                 return false;
3568                         } catch (InsufficientPermissionsException e) {
3569                                 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3570                                 return false;
3571                         }
3572
3573                 } else if (object instanceof FileHeaderDTO) {
3574                         FileHeaderDTO file = (FileHeaderDTO) object;
3575                         try {
3576                                 getService().copyFile(user.getId(), file.getId(), dest);
3577                         } catch (ObjectNotFoundException e) {
3578                                 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3579                                 return false;
3580                         } catch (DuplicateNameException e) {
3581                                 errorList.put(source, new Integer(WebdavStatus.SC_CONFLICT));
3582                                 return false;
3583                         } catch (InsufficientPermissionsException e) {
3584                                 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3585                                 return false;
3586                         } catch (QuotaExceededException e) {
3587                                 errorList.put(source, new Integer(WebdavStatus.SC_FORBIDDEN));
3588                                 return false;
3589                         } catch (GSSIOException e) {
3590                                 errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3591                                 return false;
3592                         }
3593                 } else {
3594                         errorList.put(source, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3595                         return false;
3596                 }
3597                 return true;
3598         }
3599
3600         /**
3601          * Delete a resource.
3602          *
3603          * @param req Servlet request
3604          * @param resp Servlet response
3605          * @return boolean true if the deletion is successful
3606          * @throws IOException
3607          */
3608         private boolean deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
3609                 String path = getRelativePath(req);
3610                 return deleteResource(path, req, resp, true);
3611         }
3612
3613         /**
3614          * Delete a resource.
3615          *
3616          * @param path Path of the resource which is to be deleted
3617          * @param req Servlet request
3618          * @param resp Servlet response
3619          * @param setStatus Should the response status be set on successful
3620          *            completion
3621          * @return boolean true if the deletion is successful
3622          * @throws IOException
3623          */
3624         private boolean deleteResource(String path, HttpServletRequest req, HttpServletResponse resp, boolean setStatus) throws IOException {
3625                 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3626                         resp.sendError(WebdavStatus.SC_FORBIDDEN);
3627                         return false;
3628                 }
3629                 String ifHeader = req.getHeader("If");
3630                 if (ifHeader == null)
3631                         ifHeader = "";
3632
3633                 String lockTokenHeader = req.getHeader("Lock-Token");
3634                 if (lockTokenHeader == null)
3635                         lockTokenHeader = "";
3636
3637                 if (isLocked(path, ifHeader + lockTokenHeader)) {
3638                         resp.sendError(WebdavStatus.SC_LOCKED);
3639                         return false;
3640                 }
3641
3642                 User user = getUser(req);
3643                 boolean exists = true;
3644                 Object object = null;
3645                 try {
3646                         object = getService().getResourceAtPath(user.getId(), path, true);
3647                 } catch (ObjectNotFoundException e) {
3648                         exists = false;
3649                 } catch (RpcException e) {
3650                         resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3651                         return false;
3652                 }
3653
3654                 if (!exists) {
3655                         resp.sendError(WebdavStatus.SC_NOT_FOUND);
3656                         return false;
3657                 }
3658
3659                 FolderDTO folder = null;
3660                 FileHeaderDTO file = null;
3661                 if (object instanceof FolderDTO)
3662                         folder = (FolderDTO) object;
3663                 else
3664                         file = (FileHeaderDTO) object;
3665
3666                 if (file != null)
3667                         try {
3668                                 getService().deleteFile(user.getId(), file.getId());
3669                         } catch (InsufficientPermissionsException e) {
3670                                 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
3671                                 return false;
3672                         } catch (ObjectNotFoundException e) {
3673                                 // Although we had already found the object, it was
3674                                 // probably deleted from another thread.
3675                                 resp.sendError(WebdavStatus.SC_NOT_FOUND);
3676                                 return false;
3677                         } catch (RpcException e) {
3678                                 resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
3679                                 return false;
3680                         }
3681                 else if (folder != null) {
3682                         Hashtable<String, Integer> errorList = new Hashtable<String, Integer>();
3683                         deleteCollection(req, folder, path, errorList);
3684                         try {
3685                                 getService().deleteFolder(user.getId(), folder.getId());
3686                         } catch (InsufficientPermissionsException e) {
3687                                 errorList.put(path, new Integer(WebdavStatus.SC_METHOD_NOT_ALLOWED));
3688                         } catch (ObjectNotFoundException e) {
3689                                 errorList.put(path, new Integer(WebdavStatus.SC_NOT_FOUND));
3690                         } catch (RpcException e) {
3691                                 errorList.put(path, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3692                         }
3693
3694                         if (!errorList.isEmpty()) {
3695                                 sendReport(req, resp, errorList);
3696                                 return false;
3697                         }
3698                 }
3699                 if (setStatus)
3700                         resp.setStatus(WebdavStatus.SC_NO_CONTENT);
3701                 return true;
3702         }
3703
3704         /**
3705          * Deletes a collection.
3706          *
3707          * @param req the HTTP request
3708          * @param folder the folder whose contents will be deleted
3709          * @param path Path to the collection to be deleted
3710          * @param errorList Contains the list of the errors which occurred
3711          */
3712         private void deleteCollection(HttpServletRequest req, FolderDTO folder, String path, Hashtable<String, Integer> errorList) {
3713
3714                 if (logger.isDebugEnabled())
3715                         logger.debug("Delete:" + path);
3716
3717                 if (path.toUpperCase().startsWith("/WEB-INF") || path.toUpperCase().startsWith("/META-INF")) {
3718                         errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
3719                         return;
3720                 }
3721
3722                 String ifHeader = req.getHeader("If");
3723                 if (ifHeader == null)
3724                         ifHeader = "";
3725
3726                 String lockTokenHeader = req.getHeader("Lock-Token");
3727                 if (lockTokenHeader == null)
3728                         lockTokenHeader = "";
3729
3730                 Iterator iter = folder.getSubfolders().iterator();
3731                 while (iter.hasNext()) {
3732                         FolderDTO subf = (FolderDTO) iter.next();
3733                         String childName = path;
3734                         if (!childName.equals("/"))
3735                                 childName += "/";
3736                         childName += subf.getName();
3737
3738                         if (isLocked(childName, ifHeader + lockTokenHeader))
3739                                 errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
3740                         else
3741                                 try {
3742                                         User user = getUser(req);
3743                                         Object object = getService().getResourceAtPath(user.getId(), childName, true);
3744                                         FolderDTO childFolder = null;
3745                                         FileHeaderDTO childFile = null;
3746                                         if (object instanceof FolderDTO)
3747                                                 childFolder = (FolderDTO) object;
3748                                         else
3749                                                 childFile = (FileHeaderDTO) object;
3750                                         if (childFolder != null) {
3751                                                 deleteCollection(req, childFolder, childName, errorList);
3752                                                 getService().deleteFolder(user.getId(), childFolder.getId());
3753                                         } else if (childFile != null)
3754                                                 getService().deleteFile(user.getId(), childFile.getId());
3755                                 } catch (ObjectNotFoundException e) {
3756                                         errorList.put(childName, new Integer(WebdavStatus.SC_NOT_FOUND));
3757                                 } catch (InsufficientPermissionsException e) {
3758                                         errorList.put(childName, new Integer(WebdavStatus.SC_FORBIDDEN));
3759                                 } catch (RpcException e) {
3760                                         errorList.put(childName, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
3761                                 }
3762                 }
3763         }
3764
3765         /**
3766          * Send a multistatus element containing a complete error report to the
3767          * client.
3768          *
3769          * @param req Servlet request
3770          * @param resp Servlet response
3771          * @param errorList List of error to be displayed
3772          * @throws IOException
3773          */
3774         private void sendReport(HttpServletRequest req, HttpServletResponse resp, Hashtable errorList) throws IOException {
3775
3776                 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
3777
3778                 String absoluteUri = req.getRequestURI();
3779                 String relativePath = getRelativePath(req);
3780
3781                 XMLWriter generatedXML = new XMLWriter();
3782                 generatedXML.writeXMLHeader();
3783
3784                 generatedXML.writeElement(null, "multistatus" + generateNamespaceDeclarations(), XMLWriter.OPENING);
3785
3786                 Enumeration pathList = errorList.keys();
3787                 while (pathList.hasMoreElements()) {
3788
3789                         String errorPath = (String) pathList.nextElement();
3790                         int errorCode = ((Integer) errorList.get(errorPath)).intValue();
3791
3792                         generatedXML.writeElement(null, "response", XMLWriter.OPENING);
3793
3794                         generatedXML.writeElement(null, "href", XMLWriter.OPENING);
3795                         String toAppend = errorPath.substring(relativePath.length());
3796                         if (!toAppend.startsWith("/"))
3797                                 toAppend = "/" + toAppend;
3798                         generatedXML.writeText(absoluteUri + toAppend);
3799                         generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
3800                         generatedXML.writeElement(null, "status", XMLWriter.OPENING);
3801                         generatedXML.writeText("HTTP/1.1 " + errorCode + " " + WebdavStatus.getStatusText(errorCode));
3802                         generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
3803
3804                         generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
3805
3806                 }
3807
3808                 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
3809
3810                 Writer writer = resp.getWriter();
3811                 writer.write(generatedXML.toString());
3812                 writer.close();
3813
3814         }
3815
3816         // --------------------------------------------- WebdavResolver Inner Class
3817         /**
3818          * Work around for XML parsers that don't fully respect
3819          * {@link DocumentBuilderFactory#setExpandEntityReferences(boolean)}.
3820          * External references are filtered out for security reasons. See
3821          * CVE-2007-5461.
3822          */
3823         private class WebdavResolver implements EntityResolver {
3824
3825                 /**
3826                  * A private copy of the servlet context.
3827                  */
3828                 private ServletContext context;
3829
3830                 /**
3831                  * Construct the resolver by passing the servlet context.
3832                  *
3833                  * @param theContext the servlet context
3834                  */
3835                 public WebdavResolver(ServletContext theContext) {
3836                         context = theContext;
3837                 }
3838
3839                 public InputSource resolveEntity(String publicId, String systemId) {
3840                         context.log("The request included a reference to an external entity with PublicID " + publicId + " and SystemID " + systemId + " which was ignored");
3841                         return new InputSource(new StringReader("Ignored external entity"));
3842                 }
3843         }
3844
3845         /**
3846          * Returns the user making the request. This is the user whose credentials
3847          * were supplied in the authorization header.
3848          *
3849          * @param req the HTTP request
3850          * @return the user making the request
3851          */
3852         protected User getUser(HttpServletRequest req) {
3853                 return (User) req.getAttribute(USER_ATTRIBUTE);
3854         }
3855
3856         /**
3857          * Retrieves the user who owns the requested namespace, as specified in the
3858          * REST API.
3859          *
3860          * @param req the HTTP request
3861          * @return the owner of the namespace
3862          */
3863         protected User getOwner(HttpServletRequest req) {
3864                 return (User) req.getAttribute(OWNER_ATTRIBUTE);
3865         }
3866
3867 }