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