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