166332b3adea598c8a707270375a029179a01f2f
[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                                 JSONArray permissions = json.optJSONArray("permissions");
1426                                 Set<PermissionDTO> perms = null;
1427                                 if (permissions != null)
1428                                         perms = parsePermissions(user, permissions);
1429                                 Boolean readForAll = null;
1430                                 if (json.opt("readForAll") != null)
1431                                         readForAll = json.optBoolean("readForAll");
1432                                 if (!name.isEmpty() || permissions != null || readForAll != null) {
1433                                         final String fName = name.isEmpty()? null: name;
1434                                         final Boolean freadForAll =  readForAll;
1435                                         final Set<PermissionDTO> fPerms = perms;
1436                                         FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1437                                                 @Override
1438                                                 public FolderDTO call() throws Exception {
1439                                                         return getService().updateFolder(user.getId(), folder.getId(), fName, freadForAll, fPerms);
1440                                                 }
1441
1442                                         });
1443                                         resp.getWriter().println(getNewUrl(req, folderUpdated));
1444                                 }
1445                         } else {
1446                                 final FileHeaderDTO file = (FileHeaderDTO) resource;
1447                                 String name = null;
1448                                 if (json.opt("name") != null)
1449                                         name = json.optString("name");
1450                                 Long modificationDate = null;
1451                                 if (json.optLong("modificationDate") != 0)
1452                                         modificationDate = json.optLong("modificationDate");
1453                                 Boolean versioned = null;
1454                                 if (json.opt("versioned") != null)
1455                                         versioned = json.getBoolean("versioned");
1456                                 JSONArray tagset = json.optJSONArray("tags");
1457                                 String tags = null;
1458                                 StringBuffer t = new StringBuffer();
1459                                 if (tagset != null) {
1460                                         for (int i = 0; i < tagset.length(); i++)
1461                                                 t.append(tagset.getString(i) + ',');
1462                                         tags = t.toString();
1463                                 }
1464                                 JSONArray permissions = json.optJSONArray("permissions");
1465                                 Set<PermissionDTO> perms = null;
1466                                 if (permissions != null)
1467                                         perms = parsePermissions(user, permissions);
1468                                 Boolean readForAll = null;
1469                                 if (json.opt("readForAll") != null)
1470                                         readForAll = json.optBoolean("readForAll");
1471                                 if (name != null || tags != null || modificationDate != null
1472                                                         || versioned != null || perms != null
1473                                                         || readForAll != null) {
1474                                         final String fName = name;
1475                                         final String fTags = tags;
1476                                         final Date mDate = modificationDate != null? new Date(modificationDate): null;
1477                                         final Boolean fVersioned = versioned;
1478                                         final Boolean fReadForAll = readForAll;
1479                                         final Set<PermissionDTO> fPerms = perms;
1480                                         new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1481                                                 @Override
1482                                                 public Object call() throws Exception {
1483                                                         getService().updateFile(user.getId(), file.getId(),
1484                                                                                 fName, fTags, mDate, fVersioned,
1485                                                                                 fReadForAll, fPerms);
1486                                                         return null;
1487                                                 }
1488
1489                                         });
1490                                 }
1491                         }
1492                 } catch (JSONException e) {
1493                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1494                 } catch (InsufficientPermissionsException e) {
1495                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1496                 } catch (ObjectNotFoundException e) {
1497                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1498                 } catch (DuplicateNameException e) {
1499                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1500                 } catch (RpcException e) {
1501                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1502                 } catch (Exception e) {
1503                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1504                         return;
1505                 }
1506         }
1507
1508         /**
1509          * Returns the new URL of an updated folder.
1510          */
1511         private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1512                 String parentUrl = URLDecoder.decode(getContextPath(req, true),"UTF-8");
1513                 String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8");
1514                 if (parentUrl.indexOf(fpath) != -1)
1515                         parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1516                 if(!parentUrl.endsWith("/"))
1517                         parentUrl = parentUrl+"/";
1518                 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1519                 return parentUrl;
1520         }
1521
1522         /**
1523          * Helper method to convert a JSON array of permissions into a set of
1524          * PermissionDTO objects.
1525          *
1526          * @param user the current user
1527          * @param permissions the JSON array to parse
1528          * @return the parsed set of permissions
1529          * @throws JSONException if there was an error parsing the JSON object
1530          * @throws RpcException if there was an error communicating with the EJB
1531          * @throws ObjectNotFoundException if the user could not be found
1532          * @throws UnsupportedEncodingException
1533          */
1534         private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1535                         throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1536                 if (permissions == null)
1537                         return null;
1538                 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1539                 for (int i = 0; i < permissions.length(); i++) {
1540                         JSONObject j = permissions.getJSONObject(i);
1541                         PermissionDTO perm = new PermissionDTO();
1542                         perm.setModifyACL(j.optBoolean("modifyACL"));
1543                         perm.setRead(j.optBoolean("read"));
1544                         perm.setWrite(j.optBoolean("write"));
1545                         String permUser = j.optString("user");
1546                         if (!permUser.isEmpty()) {
1547                                 User u = getService().findUser(permUser);
1548                                 if (u == null)
1549                                         throw new ObjectNotFoundException("User " + permUser + " not found");
1550                                 perm.setUser(u.getDTO());
1551                         }
1552                         // 31/8/2009: Add optional groupUri which takes priority if it exists
1553                         String permGroupUri = j.optString("groupUri");
1554                         String permGroup = j.optString("group");
1555                         if (!permGroupUri.isEmpty()) {
1556                                 String[] names = permGroupUri.split("/");
1557                                 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1558                                 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1559                                 User u = getService().findUser(usr);
1560                                 if (u == null)
1561                                         throw new ObjectNotFoundException("User " + permUser + " not found");
1562                                 GroupDTO g = getService().getGroup(u.getId(), grp);
1563                                 perm.setGroup(g);
1564                         }
1565                         else if (!permGroup.isEmpty()) {
1566                                 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1567                                 perm.setGroup(g);
1568                         }
1569                         if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1570                                 throw new JSONException("A permission must correspond to either a user or a group");
1571                         perms.add(perm);
1572                 }
1573                 return perms;
1574         }
1575
1576         /**
1577          * Creates a new folder with the specified name under the folder in the provided path.
1578          *
1579          * @param req the HTTP request
1580          * @param resp the HTTP response
1581          * @param path the parent folder path
1582          * @param folderName the name of the new folder
1583          * @throws IOException if an input/output error occurs
1584          */
1585         private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1586                 if (logger.isDebugEnabled())
1587                         logger.debug("Creating folder " + folderName + " in '" + path);
1588
1589         final User user = getUser(req);
1590         User owner = getOwner(req);
1591         boolean exists = true;
1592         try {
1593                 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1594         } catch (ObjectNotFoundException e) {
1595             exists = false;
1596         } catch (RpcException e) {
1597                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1598                         return;
1599                 }
1600
1601         if (exists) {
1602             resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1603                                 ", " + METHOD_HEAD);
1604             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1605             return;
1606         }
1607
1608                 Object parent;
1609                 try {
1610                         parent = getService().getResourceAtPath(owner.getId(), path, true);
1611                 } catch (ObjectNotFoundException e) {
1612                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1613                         return;
1614                 } catch (RpcException e) {
1615                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1616                         return;
1617                 }
1618                 try {
1619                         if (parent instanceof FolderDTO) {
1620                                 final FolderDTO folder = (FolderDTO) parent;
1621                                 FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1622                                         @Override
1623                                         public FolderDTO call() throws Exception {
1624                                                 return getService().createFolder(user.getId(), folder.getId(), folderName);
1625                                         }
1626
1627                                 });
1628                         String newResource = getApiRoot() + newFolder.getURI();
1629                         resp.setHeader("Location", newResource);
1630                         resp.setContentType("text/plain");
1631                     PrintWriter out = resp.getWriter();
1632                     out.println(newResource);
1633                         } else {
1634                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1635                         return;
1636                         }
1637                 } catch (DuplicateNameException e) {
1638                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1639                 return;
1640                 } catch (InsufficientPermissionsException e) {
1641                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1642                 return;
1643                 } catch (ObjectNotFoundException e) {
1644                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1645                         return;
1646                 } catch (RpcException e) {
1647                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1648                         return;
1649                 } catch (Exception e) {
1650                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1651                         return;
1652                 }
1653         resp.setStatus(HttpServletResponse.SC_CREATED);
1654         }
1655
1656         /**
1657          * @param req
1658          * @param resp
1659          * @throws IOException
1660          * @throws FileNotFoundException
1661          */
1662         void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1663         String path = getInnerPath(req, PATH_FILES);
1664                 try {
1665                 path = URLDecoder.decode(path, "UTF-8");
1666                 } catch (IllegalArgumentException e) {
1667                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1668                         return;
1669                 }
1670         if (logger.isDebugEnabled())
1671                         logger.debug("Updating resource: " + path);
1672
1673         final User user = getUser(req);
1674         User owner = getOwner(req);
1675         boolean exists = true;
1676         Object resource = null;
1677         FileHeaderDTO file = null;
1678         try {
1679                 resource = getService().getResourceAtPath(owner.getId(), path, false);
1680         } catch (ObjectNotFoundException e) {
1681             exists = false;
1682         } catch (RpcException e) {
1683                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1684                         return;
1685                 }
1686
1687         if (exists)
1688                         if (resource instanceof FileHeaderDTO)
1689                         file = (FileHeaderDTO) resource;
1690                         else {
1691                         resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1692                         return;
1693                 }
1694         boolean result = true;
1695
1696         // Temporary content file used to support partial PUT.
1697         File contentFile = null;
1698
1699         Range range = parseContentRange(req, resp);
1700
1701         InputStream resourceInputStream = null;
1702
1703         // Append data specified in ranges to existing content for this
1704         // resource - create a temporary file on the local filesystem to
1705         // perform this operation.
1706         // Assume just one range is specified for now
1707         if (range != null) {
1708             try {
1709                                 contentFile = executePartialPut(req, range, path);
1710                         } catch (RpcException e) {
1711                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1712                                 return;
1713                         } catch (ObjectNotFoundException e) {
1714                                 resp.sendError(HttpServletResponse.SC_CONFLICT);
1715                         return;
1716                         } catch (InsufficientPermissionsException e) {
1717                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1718                         return;
1719                         }
1720             resourceInputStream = new FileInputStream(contentFile);
1721         } else
1722                         resourceInputStream = req.getInputStream();
1723
1724         try {
1725                 FolderDTO folder = null;
1726                 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1727                 if (!(parent instanceof FolderDTO)) {
1728                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1729                         return;
1730                 }
1731                 folder = (FolderDTO) parent;
1732                 final String name = getLastElement(path);
1733                 final String mimeType = context.getMimeType(name);
1734                 File uploadedFile = null;
1735                 try {
1736                                 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1737                         } catch (IOException ex) {
1738                                 throw new GSSIOException(ex, false);
1739                         }
1740                 FileHeaderDTO fileDTO = null;
1741                 final File uploadedf = uploadedFile;
1742                         final FolderDTO parentf = folder;
1743                         final FileHeaderDTO f = file;
1744             if (exists)
1745                 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1746                                         @Override
1747                                         public FileHeaderDTO call() throws Exception {
1748                                                 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1749                                         }
1750                                 });
1751                         else
1752                                 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1753                                         @Override
1754                                         public FileHeaderDTO call() throws Exception {
1755                                                 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1756                                         }
1757
1758                                 });
1759             updateAccounting(owner, new Date(), fileDTO.getFileSize());
1760                         getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1761         } catch(ObjectNotFoundException e) {
1762             result = false;
1763         } catch (RpcException e) {
1764                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1765                         return;
1766         } catch (IOException e) {
1767                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1768                         return;
1769                 } catch (GSSIOException e) {
1770                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1771                         return;
1772                 } catch (DuplicateNameException e) {
1773                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1774                 return;
1775                 } catch (InsufficientPermissionsException e) {
1776                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1777                 return;
1778                 } catch (QuotaExceededException e) {
1779                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1780                 return;
1781                 } catch (Exception e) {
1782                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1783                         return;
1784                 }
1785
1786         if (result) {
1787             if (exists)
1788                                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1789                         else
1790                                 resp.setStatus(HttpServletResponse.SC_CREATED);
1791         } else
1792                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1793         }
1794
1795     /**
1796      * Delete a resource.
1797      *
1798      * @param req The servlet request we are processing
1799      * @param resp The servlet response we are processing
1800          * @throws IOException if the response cannot be sent
1801      */
1802     void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1803         String path = getInnerPath(req, PATH_FILES);
1804         if (logger.isDebugEnabled())
1805                         logger.debug("Deleting resource '" + path);
1806         path = URLDecoder.decode(path, "UTF-8");
1807         final User user = getUser(req);
1808         User owner = getOwner(req);
1809         boolean exists = true;
1810         Object object = null;
1811         try {
1812                 object = getService().getResourceAtPath(owner.getId(), path, false);
1813         } catch (ObjectNotFoundException e) {
1814                 exists = false;
1815         } catch (RpcException e) {
1816                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1817                         return;
1818                 }
1819
1820         if (!exists) {
1821                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1822                 return;
1823         }
1824
1825         FolderDTO folder = null;
1826         FileHeaderDTO file = null;
1827         if (object instanceof FolderDTO)
1828                 folder = (FolderDTO) object;
1829         else
1830                 file = (FileHeaderDTO) object;
1831
1832         if (file != null)
1833                         try {
1834                                 final FileHeaderDTO f = file;
1835                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1836                                         @Override
1837                                         public Void call() throws Exception {
1838                                                 getService().deleteFile(user.getId(), f.getId());
1839                                                 return null;
1840                                         }
1841                                 });
1842                 } catch (InsufficientPermissionsException e) {
1843                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1844                                 return;
1845                 } catch (ObjectNotFoundException e) {
1846                         // Although we had already found the object, it was
1847                         // probably deleted from another thread.
1848                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1849                         return;
1850                 } catch (RpcException e) {
1851                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1852                         return;
1853                 } catch (Exception e) {
1854                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1855                         return;
1856                 }
1857                 else if (folder != null)
1858                         try {
1859                                 final FolderDTO fo = folder;
1860                                 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1861                                         @Override
1862                                         public Void call() throws Exception {
1863                                                 getService().deleteFolder(user.getId(), fo.getId());
1864                                                 return null;
1865                                         }
1866                                 });
1867                 } catch (InsufficientPermissionsException e) {
1868                         resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1869                         return;
1870                 } catch (ObjectNotFoundException e) {
1871                         resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1872                         return;
1873                 } catch (RpcException e) {
1874                         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1875                         return;
1876                 } catch (Exception e) {
1877                         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1878                         return;
1879                 }
1880                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1881         return;
1882     }
1883
1884         /**
1885      * Return an InputStream to a JSON representation of the contents
1886      * of this directory.
1887      *
1888          * @param user the user that made the request
1889      * @param folder the specified directory
1890      * @return an input stream with the rendered contents
1891          * @throws IOException if the response cannot be sent
1892      * @throws ServletException
1893          * @throws InsufficientPermissionsException if the user does not have
1894          *                      the necessary privileges to read the directory
1895      */
1896     private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1897                 ServletException, InsufficientPermissionsException {
1898         JSONObject json = new JSONObject();
1899         try {
1900                         json.put("name", folder.getName()).
1901                                         put("owner", folder.getOwner().getUsername()).
1902                                         put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1903                                         put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1904                                         put("deleted", folder.isDeleted()).
1905                                         put("readForAll", folder.isReadForAll());
1906
1907                         if (folder.getAuditInfo().getModifiedBy() != null)
1908                                 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1909                                                 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1910                         if (folder.getParent() != null) {
1911                                 JSONObject j = new JSONObject();
1912                                 j.put("uri", getApiRoot() + folder.getParent().getURI());
1913                                 j.put("name", folder.getParent().getName());
1914                                 json.put("parent", j);
1915                         }
1916                 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1917                 for (FolderDTO f: folder.getSubfolders())
1918                                 if (!f.isDeleted()) {
1919                                         JSONObject j = new JSONObject();
1920                                         j.put("name", f.getName()).
1921                                                 put("uri", getApiRoot() + f.getURI());
1922                                         subfolders.add(j);
1923                                 }
1924                 json.put("folders", subfolders);
1925                 List<JSONObject> files = new ArrayList<JSONObject>();
1926                 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1927                 for (FileHeaderDTO f: fileHeaders) {
1928                         JSONObject j = new JSONObject();
1929                                 j.put("name", f.getName()).
1930                                         put("owner", f.getOwner().getUsername()).
1931                                         put("deleted", f.isDeleted()).
1932                                         put("version", f.getVersion()).
1933                                         put("content", f.getMimeType()).
1934                                         put("size", f.getFileSize()).
1935                                         put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1936                                         put("path", f.getFolder().getPath()).
1937                                         put("uri", getApiRoot() + f.getURI());
1938                                 if (f.getAuditInfo().getModificationDate() != null)
1939                                         j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1940                                 files.add(j);
1941                 }
1942                 json.put("files", files);
1943                 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1944                 json.put("permissions", renderJson(perms));
1945                 } catch (JSONException e) {
1946                         throw new ServletException(e);
1947                 } catch (ObjectNotFoundException e) {
1948                         throw new ServletException(e);
1949                 } catch (RpcException e) {
1950                         throw new ServletException(e);
1951                 }
1952
1953         // Prepare a writer to a buffered area
1954         ByteArrayOutputStream stream = new ByteArrayOutputStream();
1955         OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1956         PrintWriter writer = new PrintWriter(osWriter);
1957
1958         // Return an input stream to the underlying bytes
1959         writer.write(json.toString());
1960         writer.flush();
1961         return new ByteArrayInputStream(stream.toByteArray());
1962     }
1963
1964         /**
1965      * Return a String with a JSON representation of the metadata
1966      * of the specified folder.
1967          * @throws RpcException
1968          * @throws InsufficientPermissionsException
1969          * @throws ObjectNotFoundException
1970      */
1971     private String renderJsonMetadata(User user, FolderDTO folder)
1972                 throws ServletException, InsufficientPermissionsException {
1973         // Check if the user has read permission.
1974                 try {
1975                         if (!getService().canReadFolder(user.getId(), folder.getId()))
1976                                 throw new InsufficientPermissionsException();
1977                 } catch (ObjectNotFoundException e) {
1978                         throw new ServletException(e);
1979                 } catch (RpcException e) {
1980                         throw new ServletException(e);
1981                 }
1982
1983         JSONObject json = new JSONObject();
1984         try {
1985                         json.put("name", folder.getName()).
1986                         put("owner", folder.getOwner().getUsername()).
1987                         put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1988                         put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1989                         put("deleted", folder.isDeleted());
1990                         if (folder.getAuditInfo().getModifiedBy() != null)
1991                                 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1992                                                 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1993                 } catch (JSONException e) {
1994                         throw new ServletException(e);
1995                 }
1996         return json.toString();
1997     }
1998
1999         /**
2000      * Return a String with a JSON representation of the metadata
2001      * of the specified file. If an old file body is provided, then
2002      * the metadata of that particular version will be returned.
2003      *
2004          * @param user the user that made the request
2005      * @param file the specified file header
2006      * @param oldBody the version number
2007      * @return the JSON-encoded file
2008      * @throws ServletException
2009          * @throws InsufficientPermissionsException if the user does not have
2010          *                      the necessary privileges to read the directory
2011      */
2012     private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
2013                 throws ServletException, InsufficientPermissionsException {
2014         JSONObject json = new JSONObject();
2015         try {
2016                 // Need to encode file name in order to properly display it in the web client.
2017                         json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2018                                         put("owner", file.getOwner().getUsername()).
2019                                         put("versioned", file.isVersioned()).
2020                                         put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
2021                                         put("readForAll", file.isReadForAll()).
2022                                         put("tags", renderJson(file.getTags())).
2023                                         put("path", file.getFolder().getPath()).
2024                                 put("uri", getApiRoot() + file.getURI()).
2025                                         put("deleted", file.isDeleted());
2026                         JSONObject j = new JSONObject();
2027                         j.put("uri", getApiRoot() + file.getFolder().getURI()).
2028                                         put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2029                         json.put("folder", j);
2030                         if (oldBody != null)
2031                                 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2032                                                 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2033                                                 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2034                                                 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2035                                                 put("content", oldBody.getMimeType()).
2036                                                 put("size", oldBody.getFileSize());
2037                         else
2038                                 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2039                                                 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2040                                                 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2041                                                 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2042                                                 put("content", file.getMimeType()).
2043                                                 put("size", file.getFileSize());
2044                 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
2045                 json.put("permissions", renderJson(perms));
2046                 } catch (JSONException e) {
2047                         throw new ServletException(e);
2048                 } catch (ObjectNotFoundException e) {
2049                         throw new ServletException(e);
2050                 } catch (RpcException e) {
2051                         throw new ServletException(e);
2052                 } catch (UnsupportedEncodingException e) {
2053                         throw new ServletException(e);
2054                 }
2055
2056         return json.toString();
2057     }
2058
2059         /**
2060          * Return a String with a JSON representation of the
2061          * specified set of permissions.
2062      *
2063          * @param permissions the set of permissions
2064          * @return the JSON-encoded object
2065          * @throws JSONException
2066          * @throws UnsupportedEncodingException
2067          */
2068         private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2069                 JSONArray perms = new JSONArray();
2070                 for (PermissionDTO p: permissions) {
2071                         JSONObject permission = new JSONObject();
2072                         permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2073                         if (p.getUser() != null)
2074                                 permission.put("user", p.getUser().getUsername());
2075                         if (p.getGroup() != null) {
2076                                 GroupDTO group = p.getGroup();
2077                                 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2078                                 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2079                         }
2080                         perms.put(permission);
2081                 }
2082                 return perms;
2083         }
2084
2085         /**
2086          * Return a String with a JSON representation of the
2087          * specified collection of tags.
2088      *
2089          * @param tags the collection of tags
2090          * @return the JSON-encoded object
2091          * @throws JSONException
2092          * @throws UnsupportedEncodingException
2093          */
2094         private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2095                 JSONArray tagArray = new JSONArray();
2096                 for (String t: tags)
2097                         tagArray.put(URLEncoder.encode(t,"UTF-8"));
2098                 return tagArray;
2099         }
2100
2101         /**
2102          * Retrieves the user who owns the destination namespace, for a
2103          * copy or move request.
2104          *
2105          * @param req the HTTP request
2106          * @return the owner of the namespace
2107          */
2108         protected User getDestinationOwner(HttpServletRequest req) {
2109                 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2110         }
2111
2112         /**
2113          * A helper inner class for updating the progress status of a file upload.
2114          *
2115          * @author kman
2116          */
2117         public static class StatusProgressListener implements ProgressListener {
2118                 private int percentLogged = 0;
2119                 private long bytesTransferred = 0;
2120
2121                 private long fileSize = -100;
2122
2123                 private Long userId;
2124
2125                 private String filename;
2126
2127                 private ExternalAPI service;
2128
2129                 public StatusProgressListener(ExternalAPI aService) {
2130                         service = aService;
2131                 }
2132
2133                 /**
2134                  * Modify the userId.
2135                  *
2136                  * @param aUserId the userId to set
2137                  */
2138                 public void setUserId(Long aUserId) {
2139                         userId = aUserId;
2140                 }
2141
2142                 /**
2143                  * Modify the filename.
2144                  *
2145                  * @param aFilename the filename to set
2146                  */
2147                 public void setFilename(String aFilename) {
2148                         filename = aFilename;
2149                 }
2150
2151                 @Override
2152                 public void update(long bytesRead, long contentLength, int items) {
2153                         //monitoring per percent of bytes uploaded
2154                         bytesTransferred = bytesRead;
2155                         if (fileSize != contentLength)
2156                                 fileSize = contentLength;
2157                         int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2158
2159                         if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2160                                 if (percent != percentLogged){
2161                                         percentLogged = percent;
2162                                         try {
2163                                                 if (userId != null && filename != null)
2164                                                         service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2165                                         } catch (ObjectNotFoundException e) {
2166                                                 // Swallow the exception since it is going to be caught
2167                                                 // by previously called methods
2168                                         }
2169                                 }
2170                 }
2171         }
2172         /**
2173          * Return an InputStream to an HTML representation of the contents of this
2174          * directory.
2175          *
2176          * @param contextPath Context path to which our internal paths are relative
2177          * @param relativePath the requested relative path to the resource
2178          * @param folder the specified directory
2179          * @param req the HTTP request
2180          * @return an input stream with the rendered contents
2181          * @throws IOException
2182          * @throws ServletException
2183          */
2184         private InputStream renderHtml(String contextPath, String relativePath, FolderDTO folder, User user) throws IOException, ServletException {
2185                 String name = folder.getName();
2186                 // Prepare a writer to a buffered area
2187                 ByteArrayOutputStream stream = new ByteArrayOutputStream();
2188                 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2189                 PrintWriter writer = new PrintWriter(osWriter);
2190                 StringBuffer sb = new StringBuffer();
2191                 // rewriteUrl(contextPath) is expensive. cache result for later reuse
2192                 String rewrittenContextPath = rewriteUrl(contextPath);
2193                 // Render the page header
2194                 sb.append("<html>\r\n");
2195                 sb.append("<head>\r\n");
2196                 sb.append("<title>");
2197                 sb.append("Index of " + name);
2198                 sb.append("</title>\r\n");
2199                 sb.append("<STYLE><!--");
2200                 sb.append(GSS_CSS);
2201                 sb.append("--></STYLE> ");
2202                 sb.append("</head>\r\n");
2203                 sb.append("<body>");
2204                 sb.append("<h1>");
2205                 sb.append("Index of " + name);
2206
2207                 // Render the link to our parent (if required)
2208                 String folderPath = folder.getPath();
2209                 int indexFolderPath = relativePath.indexOf(folderPath);
2210                 String relativePathNoFolderName = relativePath.substring(0, indexFolderPath);
2211                 String parentDirectory = folderPath;
2212                 //To-do: further search in encoding folder names with special characters
2213                 //String rewrittenParentDirectory = rewriteUrl(parentDirectory);
2214                 if (parentDirectory.endsWith("/"))
2215                         parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2216                 int slash = parentDirectory.lastIndexOf('/');
2217                 parentDirectory = parentDirectory.substring(0,slash);
2218                 if (slash >= 0) {
2219                         sb.append(" - <a href=\"");
2220                         sb.append(rewrittenContextPath);
2221                         sb.append(relativePathNoFolderName);
2222                         sb.append(parentDirectory);
2223                         if (!parentDirectory.endsWith("/"))
2224                                 sb.append("/");
2225                         sb.append("\">");
2226                         sb.append("<b>");
2227                         sb.append("Up To ");
2228                         if (parentDirectory.equals(""))
2229                                 parentDirectory = "/";
2230                         sb.append(parentDirectory);
2231                         sb.append("</b>");
2232                         sb.append("</a>");
2233                 }
2234
2235                 sb.append("</h1>");
2236                 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2237
2238                 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
2239
2240                 // Render the column headings
2241                 sb.append("<tr>\r\n");
2242                 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2243                 sb.append("Name");
2244                 sb.append("</strong></font></td>\r\n");
2245                 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2246                 sb.append("Size");
2247                 sb.append("</strong></font></td>\r\n");
2248                 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2249                 sb.append("Last modified");
2250                 sb.append("</strong></font></td>\r\n");
2251                 sb.append("</tr>");
2252                 // Render the directory entries within this directory
2253                 boolean shade = false;
2254                 Iterator iter = folder.getSubfolders().iterator();
2255                 while (iter.hasNext()) {
2256                         FolderDTO subf = (FolderDTO) iter.next();
2257                         String resourceName = subf.getName();
2258                         if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2259                                 continue;
2260
2261                         sb.append("<tr");
2262                         if (shade)
2263                                 sb.append(" bgcolor=\"#eeeeee\"");
2264                         sb.append(">\r\n");
2265                         shade = !shade;
2266
2267                         sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2268                         sb.append("<a href=\"");
2269                         sb.append(rewrittenContextPath);
2270                         sb.append(relativePathNoFolderName);
2271                         sb.append(folderPath + resourceName);
2272                         sb.append("/");
2273                         sb.append("\"><tt>");
2274                         sb.append(RequestUtil.filter(resourceName));
2275                         sb.append("/");
2276                         sb.append("</tt></a></td>\r\n");
2277
2278                         sb.append("<td align=\"right\"><tt>");
2279                         sb.append("&nbsp;");
2280                         sb.append("</tt></td>\r\n");
2281
2282                         sb.append("<td align=\"right\"><tt>");
2283                         sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2284                         sb.append("</tt></td>\r\n");
2285
2286                         sb.append("</tr>\r\n");
2287                 }
2288                 List<FileHeaderDTO> files;
2289                 try {
2290                         files = getService().getFiles(user.getId(), folder.getId(), true);
2291                 } catch (ObjectNotFoundException e) {
2292                         throw new ServletException(e.getMessage());
2293                 } catch (InsufficientPermissionsException e) {
2294                         throw new ServletException(e.getMessage());
2295                 } catch (RpcException e) {
2296                         throw new ServletException(e.getMessage());
2297                 }
2298                 for (FileHeaderDTO file : files)
2299                         //Display only file resources that are marked as public
2300                         if(file.isReadForAll()){
2301                                 String resourceName = file.getName();
2302                                 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2303                                         continue;
2304
2305                                 sb.append("<tr");
2306                                 if (shade)
2307                                         sb.append(" bgcolor=\"#eeeeee\"");
2308                                 sb.append(">\r\n");
2309                                 shade = !shade;
2310
2311                                 sb.append("<td align=\"left\">&nbsp;&nbsp;\r\n");
2312                                 sb.append("<a href=\"");
2313                                 sb.append(rewrittenContextPath);
2314                                 sb.append(relativePath);
2315                                 if(!relativePath.endsWith("/"))
2316                                         sb.append("/");
2317                                 sb.append(rewriteUrl(resourceName));
2318                                 sb.append("\"><tt>");
2319                                 sb.append(RequestUtil.filter(resourceName));
2320                                 sb.append("</tt></a></td>\r\n");
2321
2322                                 sb.append("<td align=\"right\"><tt>");
2323                                 sb.append(renderSize(file.getFileSize()));
2324                                 sb.append("</tt></td>\r\n");
2325
2326                                 sb.append("<td align=\"right\"><tt>");
2327                                 sb.append(getLastModifiedHttp(file.getAuditInfo()));
2328                                 sb.append("</tt></td>\r\n");
2329
2330                                 sb.append("</tr>\r\n");
2331                         }
2332
2333                 // Render the page footer
2334                 sb.append("</table>\r\n");
2335
2336                 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2337
2338                 //sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
2339                 sb.append("</body>\r\n");
2340                 sb.append("</html>\r\n");
2341
2342                 // Return an input stream to the underlying bytes
2343                 writer.write(sb.toString());
2344                 writer.flush();
2345                 return new ByteArrayInputStream(stream.toByteArray());
2346
2347         }
2348 }