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