Removed all DTO where possible
[pithos] / src / gr / ebs / gss / server / rest / FilesHandler.java
1 /*
2  * Copyright 2008, 2009, 2010 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.rest;
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.Login;
29 import gr.ebs.gss.server.domain.FileBody;
30 import gr.ebs.gss.server.domain.FileHeader;
31 import gr.ebs.gss.server.domain.FileUploadStatus;
32 import gr.ebs.gss.server.domain.Folder;
33 import gr.ebs.gss.server.domain.Group;
34 import gr.ebs.gss.server.domain.Permission;
35 import gr.ebs.gss.server.domain.User;
36 import gr.ebs.gss.server.ejb.ExternalAPI;
37 import gr.ebs.gss.server.ejb.TransactionHelper;
38 import gr.ebs.gss.server.webdav.Range;
39 import gr.ebs.gss.server.webdav.RequestUtil;
40
41 import java.io.BufferedReader;
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.InputStreamReader;
50 import java.io.OutputStreamWriter;
51 import java.io.PrintWriter;
52 import java.io.UnsupportedEncodingException;
53 import java.net.URI;
54 import java.net.URISyntaxException;
55 import java.net.URLDecoder;
56 import java.net.URLEncoder;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Date;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.List;
64 import java.util.Set;
65 import java.util.StringTokenizer;
66 import java.util.concurrent.Callable;
67
68 import javax.servlet.ServletContext;
69 import javax.servlet.ServletException;
70 import javax.servlet.ServletOutputStream;
71 import javax.servlet.http.Cookie;
72 import javax.servlet.http.HttpServletRequest;
73 import javax.servlet.http.HttpServletResponse;
74
75 import org.apache.commons.codec.binary.Base64;
76 import org.apache.commons.fileupload.FileItemIterator;
77 import org.apache.commons.fileupload.FileItemStream;
78 import org.apache.commons.fileupload.FileUploadException;
79 import org.apache.commons.fileupload.ProgressListener;
80 import org.apache.commons.fileupload.servlet.ServletFileUpload;
81 import org.apache.commons.fileupload.util.Streams;
82 import org.apache.commons.httpclient.util.DateParseException;
83 import org.apache.commons.httpclient.util.DateUtil;
84 import org.apache.commons.logging.Log;
85 import org.apache.commons.logging.LogFactory;
86 import org.json.JSONArray;
87 import org.json.JSONException;
88 import org.json.JSONObject;
89
90
91 /**
92  * A class that handles operations on the 'files' namespace.
93  *
94  * @author past
95  */
96 public class FilesHandler extends RequestHandler {
97         /**
98          * The request parameter name for fetching a different version.
99          */
100         private static final String VERSION_PARAM = "version";
101
102         /**
103          * The request attribute containing the owner of the destination URI
104          * in a copy or move request.
105          */
106         private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
107
108         private static final int TRACK_PROGRESS_PERCENT = 5;
109
110         /**
111          * The form parameter name that contains the signature in a browser POST upload.
112          */
113         private static final String AUTHORIZATION_PARAMETER = "Authorization";
114
115         /**
116          * The form parameter name that contains the date in a browser POST upload.
117          */
118         private static final String DATE_PARAMETER = "Date";
119
120         /**
121          * The request parameter name for making an upload progress request.
122          */
123         private static final String PROGRESS_PARAMETER = "progress";
124
125         /**
126          * The request parameter name for restoring a previous version of a file.
127          */
128         private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
129
130         /**
131          * The logger.
132          */
133         private static Log logger = LogFactory.getLog(FilesHandler.class);
134
135         /**
136          * The servlet context provided by the call site.
137          */
138         private ServletContext context;
139
140         /**
141          * The style sheet for displaying the directory listings.
142          */
143         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;}";
144
145
146         /**
147          * @param servletContext
148          */
149         public FilesHandler(ServletContext servletContext) {
150                 context = servletContext;
151         }
152
153         private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
154                 try {
155                         new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
156                                 @Override
157                                 public Void call() throws Exception {
158                                         getService().updateAccounting(user, date, bandwidthDiff);
159                                         return null;
160                                 }
161                         });
162                 } catch (RuntimeException e) {
163                         throw e;
164                 } catch (Exception e) {
165                         // updateAccounting() doesn't throw any checked exceptions
166                         assert false;
167                 }
168         }
169
170         /**
171      * Serve the specified resource, optionally including the data content.
172      *
173      * @param req The servlet request we are processing
174      * @param resp The servlet response we are creating
175      * @param content Should the content be included?
176      *
177      * @exception IOException if an input/output error occurs
178      * @exception ServletException if a servlet-specified error occurs
179      * @throws RpcException
180      * @throws InsufficientPermissionsException
181      * @throws ObjectNotFoundException
182      */
183         @Override
184         protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
185                 throws IOException, ServletException {
186                 boolean authDeferred = getAuthDeferred(req);
187         String path = getInnerPath(req, PATH_FILES);
188                 if (path.equals(""))
189                         path = "/";
190                 try {
191                         path = URLDecoder.decode(path, "UTF-8");
192                 } catch (IllegalArgumentException e) {
193                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
194                         return;
195                 }
196         String progress = req.getParameter(PROGRESS_PARAMETER);
197
198         if (logger.isDebugEnabled())
199                         if (content)
200                         logger.debug("Serving resource '" +     path + "' headers and data");
201                 else
202                         logger.debug("Serving resource '" +     path + "' headers only");
203
204         User user = getUser(req);
205         User owner = getOwner(req);
206         boolean exists = true;
207         Object resource = null;
208         FileHeader file = null;
209         Folder folder = null;
210         try {
211                 resource = getService().getResourceAtPath(owner.getId(), path, false);
212         } catch (ObjectNotFoundException e) {
213             exists = false;
214         } catch (RpcException e) {
215                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
216                         return;
217                 }
218
219         if (!exists && authDeferred) {
220                 // We do not want to leak information if the request
221                 // was not authenticated.
222                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
223                 return;
224         }
225
226         if (resource instanceof Folder)
227                 folder = (Folder) resource;
228         else
229                 file = (FileHeader) resource;   // Note that file will be null, if (!exists).
230
231         // Now it's time to perform the deferred authentication check.
232                 // Since regular signature checking was already performed,
233                 // we need to check the read-all flag or the signature-in-parameters.
234                 if (authDeferred) {
235                         if (file != null && !file.isReadForAll() && content) {
236                                 // Check for GET with the signature in the request parameters.
237                                 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
238                                 String dateParam = req.getParameter(DATE_PARAMETER);
239                                 if (auth == null || dateParam == null) {
240                                         // Check for a valid authentication cookie.
241                                         if (req.getCookies() != null) {
242                                                 boolean found = false;
243                                                 for (Cookie cookie : req.getCookies())
244                                                         if (Login.AUTH_COOKIE.equals(cookie.getName())) {
245                                                                 String cookieauth = cookie.getValue();
246                                                                 int sepIndex = cookieauth.indexOf(Login.COOKIE_SEPARATOR);
247                                                                 if (sepIndex == -1) {
248                                                                         handleAuthFailure(req, resp);
249                                                                         return;
250                                                                 }
251                                                                 String username = URLDecoder.decode(cookieauth.substring(0, sepIndex), "US-ASCII");
252                                                                 user = null;
253                                                                 try {
254                                                                         user = getService().findUser(username);
255                                                                 } catch (RpcException e) {
256                                                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
257                                                                         return;
258                                                                 }
259                                                                 if (user == null) {
260                                                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
261                                                                 return;
262                                                         }
263                                                                 req.setAttribute(USER_ATTRIBUTE, user);
264                                                                 String token = cookieauth.substring(sepIndex + 1);
265                                                                 if (user.getAuthToken() == null) {
266                                                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
267                                                                         return;
268                                                                 }
269                                                                 if (!Arrays.equals(user.getAuthToken(), Base64.decodeBase64(token))) {
270                                                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
271                                                                         return;
272                                                                 }
273                                                                 found = true;
274                                                                 break;
275                                                         }
276                                                 if (!found) {
277                                                         handleAuthFailure(req, resp);
278                                                         return;
279                                                 }
280                                         } else {
281                                                 handleAuthFailure(req, resp);
282                                                 return;
283                                         }
284                                 } else {
285                                 long timestamp;
286                                         try {
287                                                 timestamp = DateUtil.parseDate(dateParam).getTime();
288                                         } catch (DateParseException e) {
289                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
290                                         return;
291                                         }
292                                 if (!isTimeValid(timestamp)) {
293                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
294                                         return;
295                                 }
296
297                                         // Fetch the Authorization parameter and find the user specified in it.
298                                         String[] authParts = auth.split(" ");
299                                         if (authParts.length != 2) {
300                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
301                                         return;
302                                 }
303                                         String username = authParts[0];
304                                         String signature = authParts[1];
305                                         user = null;
306                                         try {
307                                                 user = getService().findUser(username);
308                                         } catch (RpcException e) {
309                                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
310                                                 return;
311                                         }
312                                         if (user == null) {
313                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
314                                         return;
315                                 }
316                                         req.setAttribute(USER_ATTRIBUTE, user);
317
318                                         // Remove the servlet path from the request URI.
319                                         String p = req.getRequestURI();
320                                         String servletPath = req.getContextPath() + req.getServletPath();
321                                         p = p.substring(servletPath.length());
322                                         // Validate the signature in the Authorization parameter.
323                                         String data = req.getMethod() + dateParam + p;
324                                         if (!isSignatureValid(signature, user, data)) {
325                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
326                                         return;
327                                 }
328                                 }
329                         }
330                         else if(folder != null && folder.isReadForAll() || file != null && file.isReadForAll()){
331                                 //This case refers to a folder or file with public privileges
332                                 //For a read-for-all folder request, pretend the owner is making it.
333                                 user = owner;
334                                 req.setAttribute(USER_ATTRIBUTE, user);
335                         }else if(folder != null && !folder.isReadForAll()){
336                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
337                                 return;
338                         }
339                         else{
340                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
341                                 return;
342                         }
343                 }
344         // If the resource is not a collection, and the resource path
345         // ends with "/" or "\", return NOT FOUND.
346         if (folder == null)
347                         if (path.endsWith("/") || path.endsWith("\\")) {
348                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
349                         return;
350                 }
351
352         // Workaround for IE's broken caching behavior.
353         if (folder != null)
354                 resp.setHeader("Expires", "-1");
355
356         // A request for upload progress.
357         if (progress != null && content) {
358                 serveProgress(req, resp, progress, user, file);
359                         return;
360         }
361
362                 // Fetch the version to retrieve, if specified.
363                 String verStr = req.getParameter(VERSION_PARAM);
364                 int version = 0;
365                 FileBody oldBody = null;
366                 if (verStr != null && file != null)
367                         try {
368                                 version = Integer.valueOf(verStr);
369                         } catch (NumberFormatException e) {
370                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
371                         return;
372                         }
373                 if (version > 0)
374                         try {
375                                 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
376                         } catch (RpcException e) {
377                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
378                                 return;
379                         } catch (ObjectNotFoundException e) {
380                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
381                         return;
382                         } catch (InsufficientPermissionsException e) {
383                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
384                         return;
385                         }
386
387         // Check if the conditions specified in the optional If headers are
388         // satisfied. Doing this for folders would require recursive checking
389         // for all of their children, which in turn would defy the purpose of
390         // the optimization.
391         if (folder == null){
392                         // Checking If headers.
393                 if (!checkIfHeaders(req, resp, file, oldBody))
394                                 return;
395         }
396         else if(!checkIfModifiedSince(req, resp, folder))
397                 return;
398
399         // Find content type.
400         String contentType = null;
401         boolean isContentHtml = false;
402         boolean expectJSON = false;
403
404         if (file != null) {
405                 contentType = version>0 ? oldBody.getMimeType() : file.getCurrentBody().getMimeType();
406                 if (contentType == null) {
407                         contentType = context.getMimeType(file.getName());
408                         file.getCurrentBody().setMimeType(contentType);
409                 }
410         } else { // folder != null
411                 String accept = req.getHeader("Accept");
412                 // The order in this conditional pessimizes the common API case,
413                 // but is important for backwards compatibility with existing
414                 // clients who send no accept header and expect a JSON response.
415                 if (accept != null && accept.contains("text/html")) {
416                         contentType = "text/html;charset=UTF-8";
417                         isContentHtml = true;
418                         //this is the case when clients send the appropriate headers, the contentType is "text/html"
419                         //and expect a JSON response. The above check applies to FireGSS client
420                         expectJSON = !authDeferred ? true : false;
421                 }
422                 else {
423                         contentType = "application/json;charset=UTF-8";
424                         expectJSON = true;
425                 }
426                 }
427
428
429         ArrayList ranges = null;
430         long contentLength = -1L;
431
432         if (file != null) {
433                 // Parse range specifier.
434                 ranges = parseRange(req, resp, file, oldBody);
435                 // ETag header
436                 resp.setHeader("ETag", getETag(file, oldBody));
437                 // Last-Modified header.
438                 String lastModified = oldBody == null ?
439                                         getLastModifiedHttp(file.getAuditInfo()) :
440                                         getLastModifiedHttp(oldBody.getAuditInfo());
441                 resp.setHeader("Last-Modified", lastModified);
442                 // X-GSS-Metadata header.
443                 try {
444                                 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
445                         } catch (InsufficientPermissionsException e) {
446                                 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
447                         return;
448                 }
449                 // Get content length.
450                 contentLength = version>0 ? oldBody.getFileSize() : file.getCurrentBody().getFileSize();
451                 // Special case for zero length files, which would cause a
452                 // (silent) ISE when setting the output buffer size.
453                 if (contentLength == 0L)
454                                 content = false;
455         } else
456                 // Set the folder X-GSS-Metadata header.
457                 try {
458                                 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
459                         } catch (InsufficientPermissionsException e) {
460                                 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
461                         return;
462                 }
463
464         ServletOutputStream ostream = null;
465         PrintWriter writer = null;
466
467         if (content)
468                         try {
469                         ostream = resp.getOutputStream();
470                 } catch (IllegalStateException e) {
471                         // If it fails, we try to get a Writer instead if we're
472                         // trying to serve a text file
473                         if ( contentType == null
474                                                 || contentType.startsWith("text")
475                                                 || contentType.endsWith("xml") )
476                                         writer = resp.getWriter();
477                                 else
478                                         throw e;
479                 }
480         if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
481                 // Set the appropriate output headers
482                 if (contentType != null) {
483                         if (logger.isDebugEnabled())
484                                 logger.debug("contentType='" + contentType + "'");
485                         resp.setContentType(contentType);
486                 }
487                 if (file != null && contentLength >= 0) {
488                         if (logger.isDebugEnabled())
489                                 logger.debug("contentLength=" + contentLength);
490                         if (contentLength < Integer.MAX_VALUE)
491                                         resp.setContentLength((int) contentLength);
492
493                                 else
494                                         // Set the content-length as String to be able to use a long
495                                 resp.setHeader("content-length", "" + contentLength);
496                 }
497
498                 InputStream renderResult = null;
499                 String relativePath = getRelativePath(req);
500                 String contextPath = req.getContextPath();
501                 String servletPath = req.getServletPath();
502                 String contextServletPath = contextPath + servletPath;
503                 if (folder != null && content)
504                         // Serve the directory browser for a public folder
505                         if (isContentHtml && !expectJSON)
506                                 renderResult = renderHtml(contextServletPath, relativePath, folder,user);
507                         // Serve the directory for an ordinary folder or for fireGSS client
508                         else
509                                 try {
510                                         renderResult = renderJson(user, folder);
511                                         } catch (InsufficientPermissionsException e) {
512                                                 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
513                                                 return;
514                                         }
515
516
517                 // Copy the input stream to our output stream (if requested)
518                 if (content) {
519                         try {
520                                 resp.setBufferSize(output);
521                         } catch (IllegalStateException e) {
522                                 // Silent catch
523                         }
524                         try {
525                                 if(file != null)
526                                                 if (needsContentDisposition(req))
527                                                 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
528                                         else
529                                                 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
530                                 if (ostream != null)
531                                                 copy(file, renderResult, ostream, req, oldBody);
532                                         else
533                                                 copy(file, renderResult, writer, req, oldBody);
534                                 if (file!=null) updateAccounting(owner, new Date(), contentLength);
535                         } catch (ObjectNotFoundException e) {
536                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
537                                 return;
538                         } catch (InsufficientPermissionsException e) {
539                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
540                                 return;
541                         } catch (RpcException e) {
542                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
543                                 return;
544                         }
545                 }
546         } else {
547                 if (ranges == null || ranges.isEmpty())
548                         return;
549                 // Partial content response.
550                 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
551
552                 if (ranges.size() == 1) {
553                         Range range = (Range) ranges.get(0);
554                         resp.addHeader("Content-Range", "bytes "
555                                                 + range.start
556                                                 + "-" + range.end + "/"
557                                                 + range.length);
558                         long length = range.end - range.start + 1;
559                         if (length < Integer.MAX_VALUE)
560                                         resp.setContentLength((int) length);
561                                 else
562                                         // Set the content-length as String to be able to use a long
563                                 resp.setHeader("content-length", "" + length);
564
565                         if (contentType != null) {
566                                 if (logger.isDebugEnabled())
567                                         logger.debug("contentType='" + contentType + "'");
568                                 resp.setContentType(contentType);
569                         }
570
571                         if (content) {
572                                 try {
573                                         resp.setBufferSize(output);
574                                 } catch (IllegalStateException e) {
575                                         // Silent catch
576                                 }
577                                 try {
578                                         if (ostream != null)
579                                                         copy(file, ostream, range, req, oldBody);
580                                                 else
581                                                         copy(file, writer, range, req, oldBody);
582                                         updateAccounting(owner, new Date(), contentLength);
583                         } catch (ObjectNotFoundException e) {
584                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
585                                 return;
586                         } catch (InsufficientPermissionsException e) {
587                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
588                                 return;
589                         } catch (RpcException e) {
590                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
591                                 return;
592                         }
593                         }
594                 } else {
595                         resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
596                         if (content) {
597                                 try {
598                                         resp.setBufferSize(output);
599                                 } catch (IllegalStateException e) {
600                                         // Silent catch
601                                 }
602                                 try {
603                                         if (ostream != null)
604                                                         copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
605                                                 else
606                                                         copy(file, writer, ranges.iterator(), contentType, req, oldBody);
607                                         updateAccounting(owner, new Date(), contentLength);
608                         } catch (ObjectNotFoundException e) {
609                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
610                                 return;
611                         } catch (InsufficientPermissionsException e) {
612                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
613                                 return;
614                         } catch (RpcException e) {
615                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
616                                 return;
617                         }
618                         }
619                 }
620         }
621     }
622
623         /**
624          * Handles an authentication failure. If no Authorization or Date request
625          * parameters and no Authorization, Date or X-GSS-Date headers were present,
626          * this is a browser request, so redirect to login and then let the user get
627          * back to the file. Otherwise it's a bogus client request and Forbidden is
628          * returned.
629          */
630         private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
631                 if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
632                                 req.getParameter(DATE_PARAMETER) == null &&
633                                 req.getHeader(AUTHORIZATION_HEADER) == null &&
634                                 req.getDateHeader(DATE_HEADER) == -1 &&
635                                 req.getDateHeader(GSS_DATE_HEADER) == -1)
636                         resp.sendRedirect(getConfiguration().getString("loginUrl") +
637                                         "?next=" + req.getRequestURL().toString());
638                 else
639                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
640         }
641
642         /**
643          * Return the filename of the specified file properly formatted for
644          * including in the Content-Disposition header.
645          */
646         private String getDispositionFilename(FileHeader file) throws UnsupportedEncodingException {
647                 return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
648         }
649
650         /**
651          * Determines whether the user agent needs the Content-Disposition
652          * header to be set, in order to properly download a file.
653          *
654          * @param req the HTTP request
655          * @return true if the Content-Disposition HTTP header must be set
656          */
657         private boolean needsContentDisposition(HttpServletRequest req) {
658                 /*String agent = req.getHeader("user-agent");
659                 if (agent != null && agent.contains("MSIE"))
660                         return true;*/
661                 String dl = req.getParameter("dl");
662                 if ("1".equals(dl))
663                         return true;
664                 return false;
665         }
666
667         /**
668          * Sends a progress update on the amount of bytes received until now for
669          * a file that the current user is currently uploading.
670          *
671          * @param req the HTTP request
672          * @param resp the HTTP response
673          * @param parameter the value for the progress request parameter
674          * @param user the current user
675          * @param file the file being uploaded, or null if the request is about a new file
676          * @throws IOException if an I/O error occurs
677          */
678         private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
679                                 String parameter, User user, FileHeader file)   throws IOException {
680                 String filename = file == null ? parameter : file.getName();
681                 try {
682                         FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
683                         if (status == null) {
684                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
685                                 return;
686                         }
687                         JSONObject json = new JSONObject();
688                         json.put("bytesUploaded", status.getBytesUploaded()).
689                                 put("bytesTotal", status.getFileSize());
690                         sendJson(req, resp, json.toString());
691
692                         // Workaround for IE's broken caching behavior.
693                 resp.setHeader("Expires", "-1");
694                         return;
695                 } catch (RpcException e) {
696                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
697                         return;
698                 } catch (JSONException e) {
699                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
700                         return;
701                 }
702         }
703
704         /**
705          * Server a POST request to create/modify a file or folder.
706          *
707          * @param req the HTTP request
708          * @param resp the HTTP response
709      * @exception IOException if an input/output error occurs
710          */
711         void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
712                 boolean authDeferred = getAuthDeferred(req);
713         if (!authDeferred && req.getParameterMap().size() > 1) {
714                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
715                 return;
716         }
717         String path = getInnerPath(req, PATH_FILES);
718         path = path.endsWith("/")? path: path + '/';
719                 try {
720                 path = URLDecoder.decode(path, "UTF-8");
721                 } catch (IllegalArgumentException e) {
722                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
723                         return;
724                 }
725         // We only defer authenticating multipart POST requests.
726         if (authDeferred) {
727                         if (!ServletFileUpload.isMultipartContent(req)) {
728                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
729                         return;
730                 }
731                         handleMultipart(req, resp, path);
732                         return;
733                 }
734
735         String newName = req.getParameter(NEW_FOLDER_PARAMETER);
736
737         boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
738         boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
739         boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
740         String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
741         String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
742         String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
743
744         if (newName != null){
745                 if (!isValidResourceName(newName)) {
746                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
747                         return;
748                 }
749                         createFolder(req, resp, path, newName);
750         }
751         else if (hasUpdateParam)
752                         updateResource(req, resp, path);
753                 else if (hasTrashParam)
754                         trashResource(req, resp, path);
755                 else if (hasRestoreParam)
756                         restoreResource(req, resp, path);
757                 else if (copyTo != null)
758                         copyResource(req, resp, path, copyTo);
759                 else if (moveTo != null)
760                         moveResource(req, resp, path, moveTo);
761                 else if (restoreVersion != null)
762                         restoreVersion(req, resp, path, restoreVersion);
763                 else
764                         // IE with Gears uses POST for multiple uploads.
765                         putResource(req, resp);
766         }
767
768         /**
769          * Restores a previous version for a file.
770          *
771          * @param req the HTTP request
772          * @param resp the HTTP response
773          * @param path the resource path
774          * @param version the version number to restore
775          * @throws IOException if an I/O error occurs
776          */
777         private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
778                 final User user = getUser(req);
779                 User owner = getOwner(req);
780                 Object resource = null;
781                 try {
782                         resource = getService().getResourceAtPath(owner.getId(), path, true);
783                 } catch (ObjectNotFoundException e) {
784                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
785                         return;
786                 } catch (RpcException e) {
787                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
788                         return;
789                 }
790                 if (resource instanceof Folder) {
791                         resp.sendError(HttpServletResponse.SC_CONFLICT);
792                         return;
793                 }
794
795                 try {
796                         final FileHeader file = (FileHeader) resource;
797                         final int oldVersion = Integer.parseInt(version);
798
799                         new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
800                                 @Override
801                                 public Void call() throws Exception {
802                                         getService().restoreVersion(user.getId(), file.getId(), oldVersion);
803                                         return null;
804                                 }
805                         });
806                 } catch (InsufficientPermissionsException e) {
807                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
808                 } catch (ObjectNotFoundException e) {
809                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
810                 } catch (RpcException e) {
811                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
812                 } catch (GSSIOException e) {
813                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
814                 } catch (QuotaExceededException e) {
815                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
816                 } catch (NumberFormatException e) {
817                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
818                 } catch (Exception e) {
819                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
820                 }
821         }
822
823         /**
824          * A method for handling multipart POST requests for uploading
825          * files from browser-based JavaScript clients.
826          *
827          * @param request the HTTP request
828          * @param response the HTTP response
829          * @param path the resource path
830          * @throws IOException in case an error occurs writing to the
831          *              response stream
832          */
833         private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
834         if (logger.isDebugEnabled())
835                         logger.debug("Multipart POST for resource: " + path);
836
837         User owner = getOwner(request);
838         boolean exists = true;
839         Object resource = null;
840         FileHeader file = null;
841         try {
842                 resource = getService().getResourceAtPath(owner.getId(), path, false);
843         } catch (ObjectNotFoundException e) {
844             exists = false;
845         } catch (RpcException e) {
846                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
847                         return;
848                 }
849
850         if (exists)
851                         if (resource instanceof FileHeader) {
852                         file = (FileHeader) resource;
853                         if (file.isDeleted()) {
854                                 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
855                         return;
856                         }
857                         } else {
858                         response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
859                         return;
860                 }
861
862         Object parent;
863         String parentPath = null;
864                 try {
865                         parentPath = getParentPath(path);
866                         parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
867                 } catch (ObjectNotFoundException e) {
868                 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
869                 return;
870                 } catch (RpcException e) {
871                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
872                         return;
873                 }
874         if (!(parent instanceof Folder)) {
875                 response.sendError(HttpServletResponse.SC_CONFLICT);
876                 return;
877         }
878         final Folder folderLocal = (Folder) parent;
879         final String fileName = getLastElement(path);
880
881         if (!isValidResourceName(fileName)) {
882                 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
883                 return;
884         }
885
886                 FileItemIterator iter;
887                 File uploadedFile = null;
888                 try {
889                         // Create a new file upload handler.
890                         ServletFileUpload upload = new ServletFileUpload();
891                         StatusProgressListener progressListener = new StatusProgressListener(getService());
892                         upload.setProgressListener(progressListener);
893                         iter = upload.getItemIterator(request);
894                         String dateParam = null;
895                         String auth = null;
896                         while (iter.hasNext()) {
897                                 FileItemStream item = iter.next();
898                                 String name = item.getFieldName();
899                                 InputStream stream = item.openStream();
900                                 if (item.isFormField()) {
901                                         final String value = Streams.asString(stream);
902                                         if (name.equals(DATE_PARAMETER))
903                                                 dateParam = value;
904                                         else if (name.equals(AUTHORIZATION_PARAMETER))
905                                                 auth = value;
906
907                                         if (logger.isDebugEnabled())
908                                                 logger.debug(name + ":" + value);
909                                 } else {
910                                         // Fetch the timestamp used to guard against replay attacks.
911                                 if (dateParam == null) {
912                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
913                                         return;
914                                 }
915
916                                 long timestamp;
917                                         try {
918                                                 timestamp = DateUtil.parseDate(dateParam).getTime();
919                                         } catch (DateParseException e) {
920                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
921                                         return;
922                                         }
923                                 if (!isTimeValid(timestamp)) {
924                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
925                                         return;
926                                 }
927
928                                         // Fetch the Authorization parameter and find the user specified in it.
929                                 if (auth == null) {
930                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
931                                         return;
932                                 }
933                                         String[] authParts = auth.split(" ");
934                                         if (authParts.length != 2) {
935                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
936                                         return;
937                                 }
938                                         String username = authParts[0];
939                                         String signature = authParts[1];
940                                         User user = null;
941                                         try {
942                                                 user = getService().findUser(username);
943                                         } catch (RpcException e) {
944                                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
945                                                 return;
946                                         }
947                                         if (user == null) {
948                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
949                                         return;
950                                 }
951                                         request.setAttribute(USER_ATTRIBUTE, user);
952
953                                         // Remove the servlet path from the request URI.
954                                         String p = request.getRequestURI();
955                                         String servletPath = request.getContextPath() + request.getServletPath();
956                                         p = p.substring(servletPath.length());
957                                         // Validate the signature in the Authorization parameter.
958                                         String data = request.getMethod() + dateParam + p;
959                                         if (!isSignatureValid(signature, user, data)) {
960                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
961                                         return;
962                                 }
963
964                                         progressListener.setUserId(user.getId());
965                                         progressListener.setFilename(fileName);
966                                         final String contentType = item.getContentType();
967
968                                         try {
969                                                 uploadedFile = getService().uploadFile(stream, user.getId());
970                                         } catch (IOException ex) {
971                                                 throw new GSSIOException(ex, false);
972                                         }
973                                         FileHeader fileLocal = null;
974                                         final File upf = uploadedFile;
975                                         final FileHeader f = file;
976                                         final User u = user;
977                                         if (file == null)
978                                                 fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
979                                                         @Override
980                                                         public FileHeader call() throws Exception {
981                                                                 return getService().createFile(u.getId(), folderLocal.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
982                                                         }
983                                                 });
984                                         else
985                                                 fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
986                                                         @Override
987                                                         public FileHeader call() throws Exception {
988                                                                 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
989                                                         }
990                                                 });
991                                         updateAccounting(owner, new Date(), fileLocal.getCurrentBody().getFileSize());
992                                         getService().removeFileUploadProgress(user.getId(), fileName);
993                                 }
994                         }
995                         // We can't return 204 here since GWT's onSubmitComplete won't fire.
996                         response.setContentType("text/html");
997             response.getWriter().print("<pre></pre>");
998                 } catch (FileUploadException e) {
999                         String error = "Error while uploading file";
1000                         logger.error(error, e);
1001                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1002                 } catch (GSSIOException e) {
1003                         if (uploadedFile != null && uploadedFile.exists())
1004                                 uploadedFile.delete();
1005                         String error = "Error while uploading file";
1006                         if (e.logAsError())
1007                                 logger.error(error, e);
1008                         else
1009                                 logger.debug(error, e);
1010                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1011                 } catch (DuplicateNameException e) {
1012                         if (uploadedFile != null && uploadedFile.exists())
1013                                 uploadedFile.delete();
1014                         String error = "The specified file name already exists in this folder";
1015                         logger.error(error, e);
1016                         response.sendError(HttpServletResponse.SC_CONFLICT, error);
1017
1018                 } catch (InsufficientPermissionsException e) {
1019                         if (uploadedFile != null && uploadedFile.exists())
1020                                 uploadedFile.delete();
1021                         String error = "You don't have the necessary permissions";
1022                         logger.error(error, e);
1023                         response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1024
1025                 } catch (QuotaExceededException e) {
1026                         if (uploadedFile != null && uploadedFile.exists())
1027                                 uploadedFile.delete();
1028                         String error = "Not enough free space available";
1029                         if (logger.isDebugEnabled())
1030                                 logger.debug(error, e);
1031                         response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1032
1033                 } catch (ObjectNotFoundException e) {
1034                         if (uploadedFile != null && uploadedFile.exists())
1035                                 uploadedFile.delete();
1036                         String error = "A specified object was not found";
1037                         logger.error(error, e);
1038                         response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1039                 } catch (RpcException e) {
1040                         if (uploadedFile != null && uploadedFile.exists())
1041                                 uploadedFile.delete();
1042                         String error = "An error occurred while communicating with the service";
1043                         logger.error(error, e);
1044                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1045                 } catch (Exception e) {
1046                         if (uploadedFile != null && uploadedFile.exists())
1047                                 uploadedFile.delete();
1048                         String error = "An internal server error occurred";
1049                         logger.error(error, e);
1050                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1051                 }
1052         }
1053
1054         /**
1055          * Move the resource in the specified path to the specified destination.
1056          *
1057          * @param req the HTTP request
1058          * @param resp the HTTP response
1059          * @param path the path of the resource
1060          * @param moveTo the destination of the move procedure
1061          * @throws IOException if an input/output error occurs
1062          */
1063         private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1064                 final User user = getUser(req);
1065                 User owner = getOwner(req);
1066                 Object resource = null;
1067                 try {
1068                         resource = getService().getResourceAtPath(owner.getId(), path, true);
1069                 } catch (ObjectNotFoundException e) {
1070                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1071                         return;
1072                 } catch (RpcException e) {
1073                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1074                         return;
1075                 }
1076
1077         String destination = null;
1078         User destOwner = null;
1079                 boolean exists = true;
1080                 try {
1081                         destination = getDestinationPath(req, encodePath(moveTo));
1082                         destination = URLDecoder.decode(destination, "UTF-8");
1083                         destOwner = getDestinationOwner(req);
1084                         getService().getResourceAtPath(destOwner.getId(), destination, true);
1085                 } catch (ObjectNotFoundException e) {
1086                         exists = false;
1087                 } catch (URISyntaxException e) {
1088                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1089                         return;
1090                 } catch (RpcException e) {
1091                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1092                         return;
1093                 }
1094                 if (exists) {
1095                         resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1096                         return;
1097                 }
1098
1099                 try {
1100                         final User dOwner = destOwner;
1101                         final String dest = destination;
1102                         if (resource instanceof Folder) {
1103                                 final Folder folderLocal = (Folder) resource;
1104                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1105                                         @Override
1106                                         public Void call() throws Exception {
1107                                                 getService().moveFolderToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest);
1108                                                 return null;
1109                                         }
1110                                 });
1111                         } else {
1112                                 final FileHeader fileLocal = (FileHeader) resource;
1113                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1114                                         @Override
1115                                         public Void call() throws Exception {
1116                                                 getService().moveFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest);
1117                                                 return null;
1118                                         }
1119                                 });
1120
1121                         }
1122                 } catch (InsufficientPermissionsException e) {
1123                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1124                 } catch (ObjectNotFoundException e) {
1125                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1126                 } catch (RpcException e) {
1127                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1128                 } catch (DuplicateNameException e) {
1129                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1130                 } catch (GSSIOException e) {
1131                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1132                 } catch (QuotaExceededException e) {
1133                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1134                 } catch (Exception e) {
1135                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1136                 }
1137         }
1138
1139         /**
1140          * Copy the resource in the specified path to the specified destination.
1141          *
1142          * @param req the HTTP request
1143          * @param resp the HTTP response
1144          * @param path the path of the resource
1145          * @param copyTo the destination of the copy procedure
1146          * @throws IOException if an input/output error occurs
1147          */
1148         private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1149                 final User user = getUser(req);
1150                 User owner = getOwner(req);
1151                 Object resource = null;
1152                 try {
1153                         resource = getService().getResourceAtPath(owner.getId(), path, true);
1154                 } catch (ObjectNotFoundException e) {
1155                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1156                         return;
1157                 } catch (RpcException e) {
1158                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1159                         return;
1160                 }
1161
1162         String destination = null;
1163         User destOwner = null;
1164                 boolean exists = true;
1165                 try {
1166                         String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1167                         destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1168                         destOwner = getDestinationOwner(req);
1169                         getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1170                 } catch (ObjectNotFoundException e) {
1171                         exists = false;
1172                 } catch (URISyntaxException e) {
1173                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1174                         return;
1175                 } catch (RpcException e) {
1176                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1177                         return;
1178                 }
1179                 if (exists) {
1180                         resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1181                         return;
1182                 }
1183
1184                 try {
1185                         final User dOwner = destOwner;
1186                         final String dest = destination;
1187                         if (resource instanceof Folder) {
1188                                 final Folder folderLocal = (Folder) resource;
1189                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1190                                         @Override
1191                                         public Void call() throws Exception {
1192                                                 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest);
1193                                                 return null;
1194                                         }
1195                                 });
1196                         } else {
1197                                 final FileHeader fileLocal = (FileHeader) resource;
1198                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1199                                         @Override
1200                                         public Void call() throws Exception {
1201                                                 getService().copyFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest);
1202                                                 return null;
1203                                         }
1204                                 });
1205                         }
1206                 } catch (InsufficientPermissionsException e) {
1207                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1208                 } catch (ObjectNotFoundException e) {
1209                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1210                 } catch (RpcException e) {
1211                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1212                 } catch (DuplicateNameException e) {
1213                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1214                 } catch (GSSIOException e) {
1215                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1216                 } catch (QuotaExceededException e) {
1217                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1218                 } catch (Exception e) {
1219                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1220                 }
1221         }
1222
1223         private String encodePath(String path) throws UnsupportedEncodingException{
1224                 StringTokenizer str = new StringTokenizer(path, "/:", true);
1225                 String result = new String();
1226                 while(str.hasMoreTokens()){
1227                         String token = str.nextToken();
1228                         if(!token.equals("/") && !token.equals(":"))
1229                                 token = URLEncoder.encode(token,"UTF-8");
1230                         result = result + token;
1231                 }
1232                 return result;
1233         }
1234         /**
1235          * A helper method that extracts the relative resource path,
1236          * after removing the 'files' namespace.
1237          * The path returned is <i>not</i> URL-decoded.
1238          *
1239          * @param req the HTTP request
1240          * @param path the specified path
1241          * @return the path relative to the root folder
1242          * @throws URISyntaxException
1243          * @throws RpcException in case an error occurs while communicating
1244          *                                              with the backend
1245          * @throws UnsupportedEncodingException
1246          */
1247         private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1248                 URI uri = new URI(path);
1249                 String dest = uri.getRawPath();
1250                 // Remove the context path from the destination URI.
1251                 String contextPath = req.getContextPath();
1252                 if (!dest.startsWith(contextPath))
1253                         throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1254                 dest = dest.substring(contextPath.length());
1255                 // Remove the servlet path from the destination URI.
1256                 String servletPath = req.getServletPath();
1257                 if (!dest.startsWith(servletPath))
1258                         throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1259                 dest = dest.substring(servletPath.length());
1260         // Strip the username part
1261                 if (dest.length() < 2)
1262                         throw new URISyntaxException(dest, "No username in the destination URI");
1263                 int slash = dest.substring(1).indexOf('/');
1264                 if (slash == -1)
1265                         throw new URISyntaxException(dest, "No username in the destination URI");
1266                 // Decode the user to get the proper characters (mainly the @)
1267                 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1268                 User o;
1269                 o = getService().findUser(owner);
1270                 if (o == null)
1271                         throw new URISyntaxException(dest, "User " + owner + " not found");
1272
1273                 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1274                 dest = dest.substring(slash + 1);
1275
1276                 // Chop the resource namespace part
1277                 dest = dest.substring(RequestHandler.PATH_FILES.length());
1278
1279         dest = dest.endsWith("/")? dest: dest + '/';
1280                 return dest;
1281         }
1282
1283         /**
1284          * Move the resource in the specified path to the trash bin.
1285          *
1286          * @param req the HTTP request
1287          * @param resp the HTTP response
1288          * @param path the path of the resource
1289          * @throws IOException if an input/output error occurs
1290          */
1291         private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1292                 final User user = getUser(req);
1293                 User owner = getOwner(req);
1294                 Object resource = null;
1295                 try {
1296                         resource = getService().getResourceAtPath(owner.getId(), path, true);
1297                 } catch (ObjectNotFoundException e) {
1298                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1299                         return;
1300                 } catch (RpcException e) {
1301                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1302                         return;
1303                 }
1304
1305                 try {
1306                         if (resource instanceof Folder) {
1307                                 final Folder folderLocal = (Folder) resource;
1308                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1309                                         @Override
1310                                         public Void call() throws Exception {
1311                                                 getService().moveFolderToTrash(user.getId(), folderLocal.getId());
1312                                                 return null;
1313                                         }
1314                                 });
1315                         } else {
1316                                 final FileHeader fileLocal = (FileHeader) resource;
1317                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1318                                         @Override
1319                                         public Void call() throws Exception {
1320                                                 getService().moveFileToTrash(user.getId(), fileLocal.getId());
1321                                                 return null;
1322                                         }
1323                                 });
1324                         }
1325                 } catch (InsufficientPermissionsException e) {
1326                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1327                 } catch (ObjectNotFoundException e) {
1328                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1329                 } catch (RpcException e) {
1330                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1331                 } catch (Exception e) {
1332                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1333                 }
1334         }
1335
1336         /**
1337          * Restore the resource in the specified path from the trash bin.
1338          *
1339          * @param req the HTTP request
1340          * @param resp the HTTP response
1341          * @param path the path of the resource
1342          * @throws IOException if an input/output error occurs
1343          */
1344         private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1345                 final User user = getUser(req);
1346                 User owner = getOwner(req);
1347                 Object resource = null;
1348                 try {
1349                         resource = getService().getResourceAtPath(owner.getId(), path, false);
1350                 } catch (ObjectNotFoundException e) {
1351                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1352                         return;
1353                 } catch (RpcException e) {
1354                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1355                         return;
1356                 }
1357
1358                 try {
1359                         if (resource instanceof Folder) {
1360                                 final Folder folderLocal = (Folder) resource;
1361                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1362                                         @Override
1363                                         public Void call() throws Exception {
1364                                                 getService().removeFolderFromTrash(user.getId(), folderLocal.getId());
1365                                                 return null;
1366                                         }
1367                                 });
1368                         } else {
1369                                 final FileHeader fileLocal = (FileHeader) resource;
1370                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1371                                         @Override
1372                                         public Void call() throws Exception {
1373                                                 getService().removeFileFromTrash(user.getId(), fileLocal.getId());
1374                                                 return null;
1375                                         }
1376                                 });
1377                         }
1378                 } catch (InsufficientPermissionsException e) {
1379                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1380                 } catch (ObjectNotFoundException e) {
1381                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1382                 } catch (RpcException e) {
1383                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1384                 } catch (Exception e) {
1385                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1386                 }
1387         }
1388
1389         /**
1390          * Update the resource in the specified path.
1391          *
1392          * @param req the HTTP request
1393          * @param resp the HTTP response
1394          * @param path the path of the resource
1395          * @throws IOException if an input/output error occurs
1396          */
1397         private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1398                 final User user = getUser(req);
1399                 User owner = getOwner(req);
1400                 Object resource = null;
1401
1402                 try {
1403                         resource = getService().getResourceAtPath(owner.getId(), path, false);
1404                 } catch (ObjectNotFoundException e) {
1405                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1406                         return;
1407                 } catch (RpcException e) {
1408                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1409                         return;
1410                 }
1411                 StringBuffer input = new StringBuffer();
1412                 JSONObject json = null;
1413                 if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1414                         input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1415                 else {
1416                         // Assume application/json
1417                         BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1418                         String line = null;
1419                         while ((line = reader.readLine()) != null)
1420                                 input.append(line);
1421                         reader.close();
1422                 }
1423                 try {
1424                         json = new JSONObject(input.toString());
1425                         if (logger.isDebugEnabled())
1426                                 logger.debug("JSON update: " + json);
1427                         if (resource instanceof Folder) {
1428                                 final Folder folderLocal = (Folder) resource;
1429                                 String name = json.optString("name");
1430                                 if (!isValidResourceName(name)) {
1431                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1432                                 return;
1433                         }
1434                                 JSONArray permissions = json.optJSONArray("permissions");
1435                                 Set<Permission> perms = null;
1436                                 if (permissions != null)
1437                                         perms = parsePermissions(user, permissions);
1438                                 Boolean readForAll = null;
1439                                 if (json.opt("readForAll") != null)
1440                                         readForAll = json.optBoolean("readForAll");
1441                                 if (!name.isEmpty() || permissions != null || readForAll != null) {
1442                                         final String fName = name.isEmpty()? null: name;
1443                                         final Boolean freadForAll =  readForAll;
1444                                         final Set<Permission> fPerms = perms;
1445                                         Folder folderUpdated = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() {
1446                                                 @Override
1447                                                 public Folder call() throws Exception {
1448                                                         return getService().updateFolder(user.getId(), folderLocal.getId(), fName, freadForAll, fPerms);
1449                                                 }
1450
1451                                         });
1452                                         resp.getWriter().println(getNewUrl(req, folderUpdated));
1453                                 }
1454                         } else {
1455                                 final FileHeader fileLocal = (FileHeader) resource;
1456                                 String name = null;
1457                                 if (json.opt("name") != null)
1458                                         name = json.optString("name");
1459                                 if (name != null)
1460                                         if (!isValidResourceName(name)) {
1461                                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1462                                         return;
1463                                 }
1464                                 Long modificationDate = null;
1465                                 if (json.optLong("modificationDate") != 0)
1466                                         modificationDate = json.optLong("modificationDate");
1467                                 Boolean versioned = null;
1468                                 if (json.opt("versioned") != null)
1469                                         versioned = json.getBoolean("versioned");
1470                                 JSONArray tagset = json.optJSONArray("tags");
1471                                 String tags = null;
1472                                 StringBuffer t = new StringBuffer();
1473                                 if (tagset != null) {
1474                                         for (int i = 0; i < tagset.length(); i++)
1475                                                 t.append(tagset.getString(i) + ',');
1476                                         tags = t.toString();
1477                                 }
1478                                 JSONArray permissions = json.optJSONArray("permissions");
1479                                 Set<Permission> perms = null;
1480                                 if (permissions != null)
1481                                         perms = parsePermissions(user, permissions);
1482                                 Boolean readForAll = null;
1483                                 if (json.opt("readForAll") != null)
1484                                         readForAll = json.optBoolean("readForAll");
1485                                 if (name != null || tags != null || modificationDate != null
1486                                                         || versioned != null || perms != null
1487                                                         || readForAll != null) {
1488                                         final String fName = name;
1489                                         final String fTags = tags;
1490                                         final Date mDate = modificationDate != null? new Date(modificationDate): null;
1491                                         final Boolean fVersioned = versioned;
1492                                         final Boolean fReadForAll = readForAll;
1493                                         final Set<Permission> fPerms = perms;
1494                                         new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1495                                                 @Override
1496                                                 public Object call() throws Exception {
1497                                                         getService().updateFile(user.getId(), fileLocal.getId(),
1498                                                                                 fName, fTags, mDate, fVersioned,
1499                                                                                 fReadForAll, fPerms);
1500                                                         return null;
1501                                                 }
1502
1503                                         });
1504                                 }
1505                         }
1506                 } catch (JSONException e) {
1507                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1508                 } catch (InsufficientPermissionsException e) {
1509                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1510                 } catch (ObjectNotFoundException e) {
1511                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1512                 } catch (DuplicateNameException e) {
1513                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1514                 } catch (RpcException e) {
1515                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1516                 } catch (Exception e) {
1517                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1518                         return;
1519                 }
1520         }
1521
1522         /**
1523          * Returns the new URL of an updated folder.
1524          */
1525         private String getNewUrl(HttpServletRequest req, Folder folder) throws UnsupportedEncodingException {
1526                 String parentUrl = URLDecoder.decode(getContextPath(req, true),"UTF-8");
1527                 String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8");
1528                 if (parentUrl.indexOf(fpath) != -1)
1529                         parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1530                 if(!parentUrl.endsWith("/"))
1531                         parentUrl = parentUrl+"/";
1532                 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1533                 return parentUrl;
1534         }
1535
1536         /**
1537          * Helper method to convert a JSON array of permissions into a set of
1538          * Permission objects.
1539          *
1540          * @param user the current user
1541          * @param permissions the JSON array to parse
1542          * @return the parsed set of permissions
1543          * @throws JSONException if there was an error parsing the JSON object
1544          * @throws RpcException if there was an error communicating with the EJB
1545          * @throws ObjectNotFoundException if the user could not be found
1546          * @throws UnsupportedEncodingException
1547          */
1548         private Set<Permission> parsePermissions(User user, JSONArray permissions)
1549                         throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1550                 if (permissions == null)
1551                         return null;
1552                 Set<Permission> perms = new HashSet<Permission>();
1553                 for (int i = 0; i < permissions.length(); i++) {
1554                         JSONObject j = permissions.getJSONObject(i);
1555                         Permission perm = new Permission();
1556                         perm.setModifyACL(j.optBoolean("modifyACL"));
1557                         perm.setRead(j.optBoolean("read"));
1558                         perm.setWrite(j.optBoolean("write"));
1559                         String permUser = j.optString("user");
1560                         if (!permUser.isEmpty()) {
1561                                 User u = getService().findUser(permUser);
1562                                 if (u == null)
1563                                         throw new ObjectNotFoundException("User " + permUser + " not found");
1564                                 perm.setUser(u);
1565                         }
1566                         // 31/8/2009: Add optional groupUri which takes priority if it exists
1567                         String permGroupUri = j.optString("groupUri");
1568                         String permGroup = j.optString("group");
1569                         if (!permGroupUri.isEmpty()) {
1570                                 String[] names = permGroupUri.split("/");
1571                                 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1572                                 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1573                                 User u = getService().findUser(usr);
1574                                 if (u == null)
1575                                         throw new ObjectNotFoundException("User " + permUser + " not found");
1576                                 Group g = getService().getGroup(u.getId(), grp);
1577                                 perm.setGroup(g);
1578                         }
1579                         else if (!permGroup.isEmpty()) {
1580                                 Group g = getService().getGroup(user.getId(), permGroup);
1581                                 perm.setGroup(g);
1582                         }
1583                         if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1584                                 throw new JSONException("A permission must correspond to either a user or a group");
1585                         perms.add(perm);
1586                 }
1587                 return perms;
1588         }
1589
1590         /**
1591          * Creates a new folder with the specified name under the folder in the provided path.
1592          *
1593          * @param req the HTTP request
1594          * @param resp the HTTP response
1595          * @param path the parent folder path
1596          * @param folderName the name of the new folder
1597          * @throws IOException if an input/output error occurs
1598          */
1599         private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1600                 if (logger.isDebugEnabled())
1601                         logger.debug("Creating folder " + folderName + " in '" + path);
1602
1603         final User user = getUser(req);
1604         User owner = getOwner(req);
1605         boolean exists = true;
1606         try {
1607                 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1608         } catch (ObjectNotFoundException e) {
1609             exists = false;
1610         } catch (RpcException e) {
1611                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1612                         return;
1613                 }
1614
1615         if (exists) {
1616             resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1617                                 ", " + METHOD_HEAD);
1618             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1619             return;
1620         }
1621
1622                 Object parent;
1623                 try {
1624                         parent = getService().getResourceAtPath(owner.getId(), path, true);
1625                 } catch (ObjectNotFoundException e) {
1626                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1627                         return;
1628                 } catch (RpcException e) {
1629                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1630                         return;
1631                 }
1632                 try {
1633                         if (parent instanceof Folder) {
1634                                 final Folder folderLocal = (Folder) parent;
1635                                 Folder newFolder = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() {
1636                                         @Override
1637                                         public Folder call() throws Exception {
1638                                                 return getService().createFolder(user.getId(), folderLocal.getId(), folderName);
1639                                         }
1640
1641                                 });
1642                         String newResource = getApiRoot() + newFolder.getURI();
1643                         resp.setHeader("Location", newResource);
1644                         resp.setContentType("text/plain");
1645                     PrintWriter out = resp.getWriter();
1646                     out.println(newResource);
1647                         } else {
1648                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1649                         return;
1650                         }
1651                 } catch (DuplicateNameException e) {
1652                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1653                 return;
1654                 } catch (InsufficientPermissionsException e) {
1655                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1656                 return;
1657                 } catch (ObjectNotFoundException e) {
1658                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1659                         return;
1660                 } catch (RpcException e) {
1661                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1662                         return;
1663                 } catch (Exception e) {
1664                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1665                         return;
1666                 }
1667         resp.setStatus(HttpServletResponse.SC_CREATED);
1668         }
1669
1670         /**
1671          * @param req
1672          * @param resp
1673          * @throws IOException
1674          * @throws FileNotFoundException
1675          */
1676         void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1677         String path = getInnerPath(req, PATH_FILES);
1678                 try {
1679                 path = URLDecoder.decode(path, "UTF-8");
1680                 } catch (IllegalArgumentException e) {
1681                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1682                         return;
1683                 }
1684         if (logger.isDebugEnabled())
1685                         logger.debug("Updating resource: " + path);
1686
1687         final User user = getUser(req);
1688         User owner = getOwner(req);
1689         boolean exists = true;
1690         Object resource = null;
1691         FileHeader fileLocal = null;
1692         try {
1693                 resource = getService().getResourceAtPath(owner.getId(), path, false);
1694         } catch (ObjectNotFoundException e) {
1695             exists = false;
1696         } catch (RpcException e) {
1697                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1698                         return;
1699                 }
1700
1701         if (exists)
1702                         if (resource instanceof FileHeader)
1703                         fileLocal = (FileHeader) resource;
1704                         else {
1705                         resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1706                         return;
1707                 }
1708         boolean result = true;
1709
1710         // Temporary content file used to support partial PUT.
1711         File contentFile = null;
1712
1713         Range range = parseContentRange(req, resp);
1714
1715         InputStream resourceInputStream = null;
1716
1717         // Append data specified in ranges to existing content for this
1718         // resource - create a temporary file on the local filesystem to
1719         // perform this operation.
1720         // Assume just one range is specified for now
1721         if (range != null) {
1722             try {
1723                                 contentFile = executePartialPut(req, range, path);
1724                         } catch (RpcException e) {
1725                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1726                                 return;
1727                         } catch (ObjectNotFoundException e) {
1728                                 resp.sendError(HttpServletResponse.SC_CONFLICT);
1729                         return;
1730                         } catch (InsufficientPermissionsException e) {
1731                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1732                         return;
1733                         }
1734             resourceInputStream = new FileInputStream(contentFile);
1735         } else
1736                         resourceInputStream = req.getInputStream();
1737
1738         try {
1739                 Folder folderLocal = null;
1740                 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1741                 if (!(parent instanceof Folder)) {
1742                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1743                         return;
1744                 }
1745                 folderLocal = (Folder) parent;
1746                 final String name = getLastElement(path);
1747                 final String mimeType = context.getMimeType(name);
1748                 File uploadedFile = null;
1749                 try {
1750                                 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1751                         } catch (IOException ex) {
1752                                 throw new GSSIOException(ex, false);
1753                         }
1754                 FileHeader fileTemp = null;
1755                 final File uploadedf = uploadedFile;
1756                         final Folder parentf = folderLocal;
1757                         final FileHeader f = fileLocal;
1758             if (exists)
1759                 fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
1760                                         @Override
1761                                         public FileHeader call() throws Exception {
1762                                                 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1763                                         }
1764                                 });
1765                         else
1766                                 fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
1767                                         @Override
1768                                         public FileHeader call() throws Exception {
1769                                                 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1770                                         }
1771
1772                                 });
1773             updateAccounting(owner, new Date(), fileTemp.getCurrentBody().getFileSize());
1774                         getService().removeFileUploadProgress(user.getId(), fileTemp.getName());
1775         } catch(ObjectNotFoundException e) {
1776             result = false;
1777         } catch (RpcException e) {
1778                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1779                         return;
1780         } catch (IOException e) {
1781                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1782                         return;
1783                 } catch (GSSIOException e) {
1784                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1785                         return;
1786                 } catch (DuplicateNameException e) {
1787                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1788                 return;
1789                 } catch (InsufficientPermissionsException e) {
1790                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1791                 return;
1792                 } catch (QuotaExceededException e) {
1793                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1794                 return;
1795                 } catch (Exception e) {
1796                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1797                         return;
1798                 }
1799
1800         if (result) {
1801             if (exists)
1802                                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1803                         else
1804                                 resp.setStatus(HttpServletResponse.SC_CREATED);
1805         } else
1806                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1807         }
1808
1809     /**
1810      * Delete a resource.
1811      *
1812      * @param req The servlet request we are processing
1813      * @param resp The servlet response we are processing
1814          * @throws IOException if the response cannot be sent
1815      */
1816     void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1817         String path = getInnerPath(req, PATH_FILES);
1818         if (logger.isDebugEnabled())
1819                         logger.debug("Deleting resource '" + path);
1820         path = URLDecoder.decode(path, "UTF-8");
1821         final User user = getUser(req);
1822         User owner = getOwner(req);
1823         boolean exists = true;
1824         Object object = null;
1825         try {
1826                 object = getService().getResourceAtPath(owner.getId(), path, false);
1827         } catch (ObjectNotFoundException e) {
1828                 exists = false;
1829         } catch (RpcException e) {
1830                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1831                         return;
1832                 }
1833
1834         if (!exists) {
1835                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1836                 return;
1837         }
1838
1839         Folder folderLocal = null;
1840         FileHeader fileLocal = null;
1841         if (object instanceof Folder)
1842                 folderLocal = (Folder) object;
1843         else
1844                 fileLocal = (FileHeader) object;
1845
1846         if (fileLocal != null)
1847                         try {
1848                                 final FileHeader f = fileLocal;
1849                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1850                                         @Override
1851                                         public Void call() throws Exception {
1852                                                 getService().deleteFile(user.getId(), f.getId());
1853                                                 return null;
1854                                         }
1855                                 });
1856                 } catch (InsufficientPermissionsException e) {
1857                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1858                                 return;
1859                 } catch (ObjectNotFoundException e) {
1860                         // Although we had already found the object, it was
1861                         // probably deleted from another thread.
1862                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1863                         return;
1864                 } catch (RpcException e) {
1865                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1866                         return;
1867                 } catch (Exception e) {
1868                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1869                         return;
1870                 }
1871                 else if (folderLocal != null)
1872                         try {
1873                                 final Folder fo = folderLocal;
1874                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1875                                         @Override
1876                                         public Void call() throws Exception {
1877                                                 getService().deleteFolder(user.getId(), fo.getId());
1878                                                 return null;
1879                                         }
1880                                 });
1881                 } catch (InsufficientPermissionsException e) {
1882                         resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1883                         return;
1884                 } catch (ObjectNotFoundException e) {
1885                         resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1886                         return;
1887                 } catch (RpcException e) {
1888                         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1889                         return;
1890                 } catch (Exception e) {
1891                         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1892                         return;
1893                 }
1894                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1895         return;
1896     }
1897
1898         /**
1899      * Return an InputStream to a JSON representation of the contents
1900      * of this directory.
1901      *
1902          * @param user the user that made the request
1903      * @param folder the specified directory
1904      * @return an input stream with the rendered contents
1905          * @throws IOException if the response cannot be sent
1906      * @throws ServletException
1907          * @throws InsufficientPermissionsException if the user does not have
1908          *                      the necessary privileges to read the directory
1909      */
1910     private InputStream renderJson(User user, Folder folder) throws IOException,
1911                 ServletException, InsufficientPermissionsException {
1912         try {
1913                         folder = getService().expandFolder(folder);
1914                 } catch (ObjectNotFoundException e1) {
1915                         // TODO Auto-generated catch block
1916                         e1.printStackTrace();
1917                 } catch (RpcException e1) {
1918                         // TODO Auto-generated catch block
1919                         e1.printStackTrace();
1920                 }
1921         JSONObject json = new JSONObject();
1922         try {
1923                         json.put("name", folder.getName()).
1924                                         put("owner", folder.getOwner().getUsername()).
1925                                         put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1926                                         put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1927                                         put("deleted", folder.isDeleted()).
1928                                         put("shared", folder.getShared()).
1929                                         put("readForAll", folder.isReadForAll());
1930
1931                         if (folder.getAuditInfo().getModifiedBy() != null)
1932                                 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1933                                                 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1934                         if (folder.getParent() != null) {
1935                                 JSONObject j = new JSONObject();
1936                                 j.put("uri", getApiRoot() + folder.getParent().getURI());
1937                                 j.put("name", folder.getParent().getName());
1938                                 json.put("parent", j);
1939                         }
1940                 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1941                 for (Folder f: folder.getSubfolders())
1942                                 if (!f.isDeleted()) {
1943                                         JSONObject j = new JSONObject();
1944                                         j.put("name", f.getName()).
1945                                                 put("uri", getApiRoot() + f.getURI());
1946                                         subfolders.add(j);
1947                                 }
1948                 json.put("folders", subfolders);
1949                 List<JSONObject> files = new ArrayList<JSONObject>();
1950                 List<FileHeader> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1951                 for (FileHeader f: fileHeaders) {
1952                         JSONObject j = new JSONObject();
1953                                 j.put("name", f.getName()).
1954                                         put("owner", f.getOwner().getUsername()).
1955                                         put("deleted", f.isDeleted()).
1956                                         put("version", f.getCurrentBody().getVersion()).
1957                                         put("content", f.getCurrentBody().getMimeType()).
1958                                         put("size", f.getCurrentBody().getFileSize()).
1959                                         put("shared", f.getShared()).
1960                                         put("versioned",f.isVersioned()).
1961                                         put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1962                                         put("path", f.getFolder().getPath()).
1963                                         put("uri", getApiRoot() + f.getURI());
1964                                 if (f.getAuditInfo().getModificationDate() != null)
1965                                         j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1966                                 files.add(j);
1967                 }
1968                 json.put("files", files);
1969                 Set<Permission> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1970                 json.put("permissions", renderJson(perms));
1971                 } catch (JSONException e) {
1972                         throw new ServletException(e);
1973                 } catch (ObjectNotFoundException e) {
1974                         throw new ServletException(e);
1975                 } catch (RpcException e) {
1976                         throw new ServletException(e);
1977                 }
1978
1979         // Prepare a writer to a buffered area
1980         ByteArrayOutputStream stream = new ByteArrayOutputStream();
1981         OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1982         PrintWriter writer = new PrintWriter(osWriter);
1983
1984         // Return an input stream to the underlying bytes
1985         writer.write(json.toString());
1986         writer.flush();
1987         return new ByteArrayInputStream(stream.toByteArray());
1988     }
1989
1990         /**
1991      * Return a String with a JSON representation of the metadata
1992      * of the specified folder.
1993          * @throws RpcException
1994          * @throws InsufficientPermissionsException
1995          * @throws ObjectNotFoundException
1996      */
1997     private String renderJsonMetadata(User user, Folder folder)
1998                 throws ServletException, InsufficientPermissionsException {
1999         // Check if the user has read permission.
2000                 try {
2001                         if (!getService().canReadFolder(user.getId(), folder.getId()))
2002                                 throw new InsufficientPermissionsException();
2003                 } catch (ObjectNotFoundException e) {
2004                         throw new ServletException(e);
2005                 } catch (RpcException e) {
2006                         throw new ServletException(e);
2007                 }
2008
2009         JSONObject json = new JSONObject();
2010         try {
2011                         json.put("name", URLEncoder.encode(folder.getName(), "UTF-8")).
2012                         put("owner", folder.getOwner().getUsername()).
2013                         put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
2014                         put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
2015                         put("deleted", folder.isDeleted());
2016                         if (folder.getAuditInfo().getModifiedBy() != null)
2017                                 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
2018                                                 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
2019                 } catch (JSONException e) {
2020                         throw new ServletException(e);
2021                 }
2022         catch (UnsupportedEncodingException e) {
2023             throw new ServletException(e);
2024         }
2025         return json.toString();
2026     }
2027
2028         /**
2029      * Return a String with a JSON representation of the metadata
2030      * of the specified file. If an old file body is provided, then
2031      * the metadata of that particular version will be returned.
2032      *
2033          * @param user the user that made the request
2034      * @param file the specified file header
2035      * @param oldBody the version number
2036      * @return the JSON-encoded file
2037      * @throws ServletException
2038          * @throws InsufficientPermissionsException if the user does not have
2039          *                      the necessary privileges to read the directory
2040      */
2041     private String renderJson(User user, FileHeader file, FileBody oldBody)
2042                 throws ServletException, InsufficientPermissionsException {
2043         JSONObject json = new JSONObject();
2044         try {
2045                 file=getService().expandFile(file);
2046                 // Need to encode file name in order to properly display it in the web client.
2047                         json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2048                                         put("owner", file.getOwner().getUsername()).
2049                                         put("versioned", file.isVersioned()).
2050                                         put("version", oldBody != null ? oldBody.getVersion() : file.getCurrentBody().getVersion()).
2051                                         put("readForAll", file.isReadForAll()).
2052                                         put("shared", file.getShared()).
2053                                         put("tags", renderJson(file.getFileTagsAsStrings())).
2054                                         put("path", file.getFolder().getPath()).
2055                                 put("uri", getApiRoot() + file.getURI()).
2056                                         put("deleted", file.isDeleted());
2057                         JSONObject j = new JSONObject();
2058                         j.put("uri", getApiRoot() + file.getFolder().getURI()).
2059                                         put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2060                         json.put("folder", j);
2061                         if (oldBody != null)
2062                                 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2063                                                 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2064                                                 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2065                                                 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2066                                                 put("content", oldBody.getMimeType()).
2067                                                 put("size", oldBody.getFileSize());
2068                         else
2069                                 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2070                                                 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2071                                                 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2072                                                 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2073                                                 put("content", file.getCurrentBody().getMimeType()).
2074                                                 put("size", file.getCurrentBody().getFileSize());
2075                 Set<Permission> perms = getService().getFilePermissions(user.getId(), file.getId());
2076                 json.put("permissions", renderJson(perms));
2077                 } catch (JSONException e) {
2078                         throw new ServletException(e);
2079                 } catch (ObjectNotFoundException e) {
2080                         throw new ServletException(e);
2081                 } catch (RpcException e) {
2082                         throw new ServletException(e);
2083                 } catch (UnsupportedEncodingException e) {
2084                         throw new ServletException(e);
2085                 }
2086
2087         return json.toString();
2088     }
2089
2090         /**
2091          * Return a String with a JSON representation of the
2092          * specified set of permissions.
2093      *
2094          * @param permissions the set of permissions
2095          * @return the JSON-encoded object
2096          * @throws JSONException
2097          * @throws UnsupportedEncodingException
2098          */
2099         private JSONArray renderJson(Set<Permission> permissions) throws JSONException, UnsupportedEncodingException {
2100                 JSONArray perms = new JSONArray();
2101                 for (Permission p: permissions) {
2102                         JSONObject permission = new JSONObject();
2103                         permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2104                         if (p.getUser() != null)
2105                                 permission.put("user", p.getUser().getUsername());
2106                         if (p.getGroup() != null) {
2107                                 Group group = p.getGroup();
2108                                 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2109                                 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2110                         }
2111                         perms.put(permission);
2112                 }
2113                 return perms;
2114         }
2115
2116         /**
2117          * Return a String with a JSON representation of the
2118          * specified collection of tags.
2119      *
2120          * @param tags the collection of tags
2121          * @return the JSON-encoded object
2122          * @throws JSONException
2123          * @throws UnsupportedEncodingException
2124          */
2125         private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2126                 JSONArray tagArray = new JSONArray();
2127                 for (String t: tags)
2128                         tagArray.put(URLEncoder.encode(t,"UTF-8"));
2129                 return tagArray;
2130         }
2131
2132         /**
2133          * Retrieves the user who owns the destination namespace, for a
2134          * copy or move request.
2135          *
2136          * @param req the HTTP request
2137          * @return the owner of the namespace
2138          */
2139         protected User getDestinationOwner(HttpServletRequest req) {
2140                 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2141         }
2142
2143         /**
2144          * A helper inner class for updating the progress status of a file upload.
2145          *
2146          * @author kman
2147          */
2148         public static class StatusProgressListener implements ProgressListener {
2149                 private int percentLogged = 0;
2150                 private long bytesTransferred = 0;
2151
2152                 private long fileSize = -100;
2153
2154                 private Long userId;
2155
2156                 private String filename;
2157
2158                 private ExternalAPI service;
2159
2160                 public StatusProgressListener(ExternalAPI aService) {
2161                         service = aService;
2162                 }
2163
2164                 /**
2165                  * Modify the userId.
2166                  *
2167                  * @param aUserId the userId to set
2168                  */
2169                 public void setUserId(Long aUserId) {
2170                         userId = aUserId;
2171                 }
2172
2173                 /**
2174                  * Modify the filename.
2175                  *
2176                  * @param aFilename the filename to set
2177                  */
2178                 public void setFilename(String aFilename) {
2179                         filename = aFilename;
2180                 }
2181
2182                 @Override
2183                 public void update(long bytesRead, long contentLength, int items) {
2184                         //monitoring per percent of bytes uploaded
2185                         bytesTransferred = bytesRead;
2186                         if (fileSize != contentLength)
2187                                 fileSize = contentLength;
2188                         int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2189                         if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2190                                 if (percent != percentLogged){
2191                                         percentLogged = percent;
2192                                         try {
2193                                                 if (userId != null && filename != null)
2194                                                         service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2195                                         } catch (ObjectNotFoundException e) {
2196                                                 // Swallow the exception since it is going to be caught
2197                                                 // by previously called methods
2198                                         }
2199                                 }
2200                 }
2201         }
2202
2203         /**
2204          * Return an InputStream to an HTML representation of the contents of this
2205          * directory.
2206          *
2207          * @param contextPath Context path to which our internal paths are relative
2208          * @param path the requested path to the resource
2209          * @param folder the specified directory
2210          * @param user the specified user
2211          * @return an input stream with the rendered contents
2212          * @throws IOException
2213          * @throws ServletException
2214          */
2215         private InputStream renderHtml(String contextPath, String path, Folder folder, User user)
2216                 throws IOException, ServletException {
2217                 String name = folder.getName();
2218                 // Prepare a writer to a buffered area
2219                 ByteArrayOutputStream stream = new ByteArrayOutputStream();
2220                 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2221                 PrintWriter writer = new PrintWriter(osWriter);
2222                 StringBuffer sb = new StringBuffer();
2223                 // rewriteUrl(contextPath) is expensive. cache result for later reuse
2224                 String rewrittenContextPath = rewriteUrl(contextPath);
2225                 // Render the page header
2226                 sb.append("<html>\r\n");
2227                 sb.append("<head>\r\n");
2228                 sb.append("<title>");
2229                 sb.append("Index of " + name);
2230                 sb.append("</title>\r\n");
2231                 sb.append("<STYLE><!--");
2232                 sb.append(GSS_CSS);
2233                 sb.append("--></STYLE> ");
2234                 sb.append("</head>\r\n");
2235                 sb.append("<body>");
2236                 sb.append("<h1>");
2237                 sb.append("Index of " + name);
2238
2239                 // Render the link to our parent (if required)
2240                 String parentDirectory = path;
2241                 if (parentDirectory.endsWith("/"))
2242                         parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2243                 int slash = parentDirectory.lastIndexOf('/');
2244                 if (slash >= 0) {
2245                         String parent = path.substring(0, slash);
2246                         sb.append(" - <a href=\"");
2247                         sb.append(rewrittenContextPath);
2248                         if (parent.equals(""))
2249                                 parent = "/";
2250                         sb.append(parent);
2251                         if (!parent.endsWith("/"))
2252                                 sb.append("/");
2253                         sb.append("\">");
2254                         sb.append("<b>");
2255                         sb.append("Up To " + parent);
2256                         sb.append("</b>");
2257                         sb.append("</a>");
2258                 }
2259
2260                 sb.append("</h1>");
2261                 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2262
2263                 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
2264
2265                 // Render the column headings
2266                 sb.append("<tr>\r\n");
2267                 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2268                 sb.append("Name");
2269                 sb.append("</strong></font></td>\r\n");
2270                 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2271                 sb.append("Size");
2272                 sb.append("</strong></font></td>\r\n");
2273                 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2274                 sb.append("Last modified");
2275                 sb.append("</strong></font></td>\r\n");
2276                 sb.append("</tr>");
2277                 // Render the directory entries within this directory
2278                 boolean shade = false;
2279                 Iterator iter = folder.getSubfolders().iterator();
2280                 while (iter.hasNext()) {
2281                         Folder subf = (Folder) iter.next();
2282                         if(subf.isReadForAll() && !subf.isDeleted()){
2283                                 String resourceName = subf.getName();
2284                                 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2285                                         continue;
2286
2287                                 sb.append("<tr");
2288                                 if (shade)
2289                                         sb.append(" bgcolor=\"#eeeeee\"");
2290                                 sb.append(">\r\n");
2291                                 shade = !shade;
2292
2293                                 sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2294                                 sb.append("<a href=\"");
2295                                 sb.append(rewrittenContextPath+path);
2296                                 sb.append(rewriteUrl(resourceName));
2297                                 sb.append("/");
2298                                 sb.append("\"><tt>");
2299                                 sb.append(RequestUtil.filter(resourceName));
2300                                 sb.append("/");
2301                                 sb.append("</tt></a></td>\r\n");
2302
2303                                 sb.append("<td align=\"right\"><tt>");
2304                                 sb.append("&nbsp;");
2305                                 sb.append("</tt></td>\r\n");
2306
2307                                 sb.append("<td align=\"right\"><tt>");
2308                                 sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2309                                 sb.append("</tt></td>\r\n");
2310
2311                                 sb.append("</tr>\r\n");
2312
2313                         }
2314                 }
2315                 List<FileHeader> files;
2316                 try {
2317                         files = getService().getFiles(user.getId(), folder.getId(), true);
2318                 } catch (ObjectNotFoundException e) {
2319                         throw new ServletException(e.getMessage());
2320                 } catch (InsufficientPermissionsException e) {
2321                         throw new ServletException(e.getMessage());
2322                 } catch (RpcException e) {
2323                         throw new ServletException(e.getMessage());
2324                 }
2325                 for (FileHeader file : files)
2326                 //Display only file resources that are marked as public and are not deleted
2327                         if(file.isReadForAll() && !file.isDeleted()){
2328                                 String resourceName = file.getName();
2329                                 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2330                                         continue;
2331
2332                                 sb.append("<tr");
2333                                 if (shade)
2334                                         sb.append(" bgcolor=\"#eeeeee\"");
2335                                 sb.append(">\r\n");
2336                                 shade = !shade;
2337
2338                                 sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2339                                 sb.append("<a href=\"");
2340                                 sb.append(rewrittenContextPath + path);
2341                                 sb.append(rewriteUrl(resourceName));
2342                                 sb.append("\"><tt>");
2343                                 sb.append(RequestUtil.filter(resourceName));
2344                                 sb.append("</tt></a></td>\r\n");
2345
2346                                 sb.append("<td align=\"right\"><tt>");
2347                                 sb.append(renderSize(file.getCurrentBody().getFileSize()));
2348                                 sb.append("</tt></td>\r\n");
2349
2350                                 sb.append("<td align=\"right\"><tt>");
2351                                 sb.append(getLastModifiedHttp(file.getAuditInfo()));
2352                                 sb.append("</tt></td>\r\n");
2353
2354                                 sb.append("</tr>\r\n");
2355                         }
2356
2357                 // Render the page footer
2358                 sb.append("</table>\r\n");
2359
2360                 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2361                 sb.append("</body>\r\n");
2362                 sb.append("</html>\r\n");
2363
2364                 // Return an input stream to the underlying bytes
2365                 writer.write(sb.toString());
2366                 writer.flush();
2367                 return new ByteArrayInputStream(stream.toByteArray());
2368
2369         }
2370 }