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