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