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