e2f734c5a58a5caeec61958c735a1d55de888f8e
[pithos] / gss / 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 gr.ebs.gss.client.domain.FileBodyDTO;
22 import gr.ebs.gss.client.domain.FileHeaderDTO;
23 import gr.ebs.gss.client.domain.FolderDTO;
24 import gr.ebs.gss.client.domain.GroupDTO;
25 import gr.ebs.gss.client.domain.PermissionDTO;
26 import gr.ebs.gss.client.exceptions.DuplicateNameException;
27 import gr.ebs.gss.client.exceptions.GSSIOException;
28 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
29 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
30 import gr.ebs.gss.client.exceptions.QuotaExceededException;
31 import gr.ebs.gss.client.exceptions.RpcException;
32 import gr.ebs.gss.server.domain.FileUploadStatus;
33 import gr.ebs.gss.server.domain.User;
34 import gr.ebs.gss.server.ejb.ExternalAPI;
35 import gr.ebs.gss.server.webdav.Range;
36
37 import java.io.BufferedReader;
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.InputStreamReader;
46 import java.io.OutputStreamWriter;
47 import java.io.PrintWriter;
48 import java.io.UnsupportedEncodingException;
49 import java.net.URI;
50 import java.net.URISyntaxException;
51 import java.net.URLDecoder;
52 import java.net.URLEncoder;
53 import java.util.ArrayList;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.Set;
57 import java.util.StringTokenizer;
58
59 import javax.servlet.ServletContext;
60 import javax.servlet.ServletException;
61 import javax.servlet.ServletOutputStream;
62 import javax.servlet.http.HttpServletRequest;
63 import javax.servlet.http.HttpServletResponse;
64
65 import org.apache.commons.fileupload.FileItemIterator;
66 import org.apache.commons.fileupload.FileItemStream;
67 import org.apache.commons.fileupload.FileUploadException;
68 import org.apache.commons.fileupload.ProgressListener;
69 import org.apache.commons.fileupload.servlet.ServletFileUpload;
70 import org.apache.commons.fileupload.util.Streams;
71 import org.apache.commons.httpclient.util.DateParseException;
72 import org.apache.commons.httpclient.util.DateUtil;
73 import org.apache.commons.logging.Log;
74 import org.apache.commons.logging.LogFactory;
75 import org.json.JSONArray;
76 import org.json.JSONException;
77 import org.json.JSONObject;
78
79
80 /**
81  * A class that handles operations on the 'files' namespace.
82  *
83  * @author past
84  */
85 public class FilesHandler extends RequestHandler {
86         /**
87          * The request parameter name for fetching a different version.
88          */
89         private static final String VERSION_PARAM = "version";
90
91         /**
92          * The request attribute containing the owner of the destination URI
93          * in a copy or move request.
94          */
95         private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
96
97         private static final int TRACK_PROGRESS_PERCENT = 5;
98
99         /**
100          * The form parameter name that contains the signature in a browser POST upload.
101          */
102         private static final String AUTHORIZATION_PARAMETER = "Authorization";
103
104         /**
105          * The form parameter name that contains the date in a browser POST upload.
106          */
107         private static final String DATE_PARAMETER = "Date";
108
109         /**
110          * The request parameter name for making an upload progress request.
111          */
112         private static final String PROGRESS_PARAMETER = "progress";
113
114         /**
115          * The logger.
116          */
117         private static Log logger = LogFactory.getLog(FilesHandler.class);
118
119         /**
120          * The servlet context provided by the call site.
121          */
122         private ServletContext context;
123
124         /**
125          * @param servletContext
126          */
127         public FilesHandler(ServletContext servletContext) {
128                 context = servletContext;
129         }
130
131         /**
132      * Serve the specified resource, optionally including the data content.
133      *
134      * @param req The servlet request we are processing
135      * @param resp The servlet response we are creating
136      * @param content Should the content be included?
137      *
138      * @exception IOException if an input/output error occurs
139      * @exception ServletException if a servlet-specified error occurs
140      * @throws RpcException
141      * @throws InsufficientPermissionsException
142      * @throws ObjectNotFoundException
143      */
144         @Override
145         protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
146                 throws IOException, ServletException {
147                 boolean authDeferred = getAuthDeferred(req);
148         String path = getInnerPath(req, PATH_FILES);
149                 if (path.equals(""))
150                         path = "/";
151                 path = URLDecoder.decode(path, "UTF-8");
152         String progress = req.getParameter(PROGRESS_PARAMETER);
153
154         if (logger.isDebugEnabled())
155                         if (content)
156                         logger.debug("Serving resource '" +     path + "' headers and data");
157                 else
158                         logger.debug("Serving resource '" +     path + "' headers only");
159
160         User user = getUser(req);
161         User owner = getOwner(req);
162         if (user == null) user = owner;
163         boolean exists = true;
164         Object resource = null;
165         FileHeaderDTO file = null;
166         FolderDTO folder = null;
167         try {
168                 resource = getService().getResourceAtPath(owner.getId(), path, false);
169         } catch (ObjectNotFoundException e) {
170             exists = false;
171         } catch (RpcException e) {
172                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
173                         return;
174                 }
175
176         if (!exists) {
177                         if (authDeferred) {
178                                 // We do not want to leak information if the request
179                                 // was not authenticated.
180                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
181                                 return;
182                         }
183                 // A request for upload progress.
184                 if (progress != null && content) {
185                         serveProgress(req, resp, progress, user, null);
186                                 return;
187                 }
188
189                 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
190                 return;
191         }
192
193         if (resource instanceof FolderDTO)
194                 folder = (FolderDTO) resource;
195         else
196                 file = (FileHeaderDTO) resource;
197
198         // Now it's time to perform the deferred authentication check.
199                 // Since regular signature checking was already performed,
200                 // we need to check the read-all flag or the signature-in-parameters.
201                 if (authDeferred)
202                         if (file != null && !file.isReadForAll() && content) {
203                                 // Check for GET with the signature in the request parameters.
204                                 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
205                                 String dateParam = req.getParameter(DATE_PARAMETER);
206                                 if (auth == null || dateParam == null) {
207                                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
208                                         return;
209                                 }
210
211                         long timestamp;
212                                 try {
213                                         timestamp = DateUtil.parseDate(dateParam).getTime();
214                                 } catch (DateParseException e) {
215                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
216                                 return;
217                                 }
218                         if (!isTimeValid(timestamp)) {
219                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
220                                 return;
221                         }
222
223                                 // Fetch the Authorization parameter and find the user specified in it.
224                                 String[] authParts = auth.split(" ");
225                                 if (authParts.length != 2) {
226                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
227                                 return;
228                         }
229                                 String username = authParts[0];
230                                 String signature = authParts[1];
231                                 user = null;
232                                 try {
233                                         user = getService().findUser(username);
234                                 } catch (RpcException e) {
235                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
236                                         return;
237                                 }
238                                 if (user == null) {
239                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
240                                 return;
241                         }
242                                 req.setAttribute(USER_ATTRIBUTE, user);
243
244                                 // Validate the signature in the Authorization parameter.
245                                 String data = req.getMethod() + dateParam + URLEncoder.encode(req.getPathInfo(), "UTF-8");
246                                 if (!isSignatureValid(signature, user, data)) {
247                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
248                                 return;
249                         }
250                         } else if (file != null && !file.isReadForAll() || file == null) {
251                                 // Check for a read-for-all file request.
252                                 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
253                                 return;
254                         }
255
256         // If the resource is not a collection, and the resource path
257         // ends with "/" or "\", return NOT FOUND.
258         if (folder == null)
259                         if (path.endsWith("/") || path.endsWith("\\")) {
260                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
261                         return;
262                 }
263
264         // A request for upload progress.
265         if (progress != null && content) {
266                 if (file == null) {
267                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
268                         return;
269                 }
270                 serveProgress(req, resp, progress, user, file);
271                         return;
272         }
273
274                 // Fetch the version to retrieve, if specified.
275                 String verStr = req.getParameter(VERSION_PARAM);
276                 int version = 0;
277                 FileBodyDTO oldBody = null;
278                 if (verStr != null && file != null)
279                         try {
280                                 version = Integer.valueOf(verStr);
281                         } catch (NumberFormatException e) {
282                                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
283                         return;
284                         }
285                 if (version > 0)
286                         try {
287                                 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
288                         } catch (RpcException e) {
289                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
290                                 return;
291                         } catch (ObjectNotFoundException e) {
292                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
293                         return;
294                         } catch (InsufficientPermissionsException e) {
295                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
296                         return;
297                         }
298
299         // Check if the conditions specified in the optional If headers are
300         // satisfied. Doing this for folders would require recursive checking
301         // for all of their children, which in turn would defy the purpose of
302         // the optimization.
303         if (folder == null)
304                         // Checking If headers.
305                 if (!checkIfHeaders(req, resp, file, oldBody))
306                                 return;
307
308         // Find content type.
309         String contentType = null;
310         if (file != null) {
311                 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
312                 if (contentType == null) {
313                         contentType = context.getMimeType(file.getName());
314                         file.setMimeType(contentType);
315                 }
316         } else
317                         contentType = "application/json;charset=UTF-8";
318
319         ArrayList ranges = null;
320         long contentLength = -1L;
321
322         if (file != null) {
323                 // Parse range specifier
324                 ranges = parseRange(req, resp, file, oldBody);
325                 // ETag header
326                 resp.setHeader("ETag", getETag(file, oldBody));
327                 // Last-Modified header
328                 String lastModified = oldBody == null ?
329                                         getLastModifiedHttp(file.getAuditInfo()) :
330                                         getLastModifiedHttp(oldBody.getAuditInfo());
331                 resp.setHeader("Last-Modified", lastModified);
332                 // X-GSS-Metadata header
333                 try {
334                                 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
335                         } catch (InsufficientPermissionsException e) {
336                                 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
337                         return;
338                 }
339                 // Get content length
340                 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
341                 // Special case for zero length files, which would cause a
342                 // (silent) ISE when setting the output buffer size
343                 if (contentLength == 0L)
344                                 content = false;
345         }
346
347         ServletOutputStream ostream = null;
348         PrintWriter writer = null;
349
350         if (content)
351                         try {
352                         ostream = resp.getOutputStream();
353                 } catch (IllegalStateException e) {
354                         // If it fails, we try to get a Writer instead if we're
355                         // trying to serve a text file
356                         if ( contentType == null
357                                                 || contentType.startsWith("text")
358                                                 || contentType.endsWith("xml") )
359                                         writer = resp.getWriter();
360                                 else
361                                         throw e;
362                 }
363
364         if (folder != null
365                                 || (ranges == null || ranges.isEmpty())
366                                                         && req.getHeader("Range") == null
367                                                         || ranges == FULL) {
368                 // Set the appropriate output headers
369                 if (contentType != null) {
370                         if (logger.isDebugEnabled())
371                                 logger.debug("contentType='" + contentType + "'");
372                         resp.setContentType(contentType);
373                 }
374                 if (file != null && contentLength >= 0) {
375                         if (logger.isDebugEnabled())
376                                 logger.debug("contentLength=" + contentLength);
377                         if (contentLength < Integer.MAX_VALUE)
378                                         resp.setContentLength((int) contentLength);
379                                 else
380                                         // Set the content-length as String to be able to use a long
381                                 resp.setHeader("content-length", "" + contentLength);
382                 }
383
384                 InputStream renderResult = null;
385                 if (folder != null)
386                                 if (content) {
387                                         // Serve the directory browser
388                                 String parentUrl = getContextPath(req, true);
389                                 try {
390                                                 renderResult = renderJson(user, folder, parentUrl);
391                                         } catch (InsufficientPermissionsException e) {
392                                                 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
393                                         return;
394                                         }
395                                 }
396                 // Copy the input stream to our output stream (if requested)
397                 if (content) {
398                         try {
399                                 resp.setBufferSize(output);
400                         } catch (IllegalStateException e) {
401                                 // Silent catch
402                         }
403                         try {
404                                 if (ostream != null)
405                                                 copy(file, renderResult, ostream, req, oldBody);
406                                         else
407                                                 copy(file, renderResult, writer, req, oldBody);
408                         } catch (ObjectNotFoundException e) {
409                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
410                                 return;
411                         } catch (InsufficientPermissionsException e) {
412                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
413                                 return;
414                         } catch (RpcException e) {
415                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
416                                 return;
417                         }
418                 }
419         } else {
420                 if (ranges == null || ranges.isEmpty())
421                         return;
422                 // Partial content response.
423                 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
424
425                 if (ranges.size() == 1) {
426                         Range range = (Range) ranges.get(0);
427                         resp.addHeader("Content-Range", "bytes "
428                                                 + range.start
429                                                 + "-" + range.end + "/"
430                                                 + range.length);
431                         long length = range.end - range.start + 1;
432                         if (length < Integer.MAX_VALUE)
433                                         resp.setContentLength((int) length);
434                                 else
435                                         // Set the content-length as String to be able to use a long
436                                 resp.setHeader("content-length", "" + length);
437
438                         if (contentType != null) {
439                                 if (logger.isDebugEnabled())
440                                         logger.debug("contentType='" + contentType + "'");
441                                 resp.setContentType(contentType);
442                         }
443
444                         if (content) {
445                                 try {
446                                         resp.setBufferSize(output);
447                                 } catch (IllegalStateException e) {
448                                         // Silent catch
449                                 }
450                                 try {
451                                         if (ostream != null)
452                                                         copy(file, ostream, range, req, oldBody);
453                                                 else
454                                                         copy(file, writer, range, req, oldBody);
455                         } catch (ObjectNotFoundException e) {
456                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
457                                 return;
458                         } catch (InsufficientPermissionsException e) {
459                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
460                                 return;
461                         } catch (RpcException e) {
462                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
463                                 return;
464                         }
465                         }
466                 } else {
467                         resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
468                         if (content) {
469                                 try {
470                                         resp.setBufferSize(output);
471                                 } catch (IllegalStateException e) {
472                                         // Silent catch
473                                 }
474                                 try {
475                                         if (ostream != null)
476                                                         copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
477                                                 else
478                                                         copy(file, writer, ranges.iterator(), contentType, req, oldBody);
479                         } catch (ObjectNotFoundException e) {
480                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
481                                 return;
482                         } catch (InsufficientPermissionsException e) {
483                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
484                                 return;
485                         } catch (RpcException e) {
486                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
487                                 return;
488                         }
489                         }
490                 }
491         }
492     }
493
494         /**
495          * Sends a progress update on the amount of bytes received until now for
496          * a file that the current user is currently uploading.
497          *
498          * @param req the HTTP request
499          * @param resp the HTTP response
500          * @param parameter the value for the progress request parameter
501          * @param user the current user
502          * @param file the file being uploaded, or null if the request is about a new file
503          * @throws IOException if an I/O error occurs
504          */
505         private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
506                                 String parameter, User user, FileHeaderDTO file)        throws IOException {
507                 String filename = file == null ? parameter : file.getName();
508                 try {
509                         FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
510                         if (status == null) {
511                                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
512                                 return;
513                         }
514                         JSONObject json = new JSONObject();
515                         json.put("bytesUploaded", status.getBytesUploaded()).
516                                 put("bytesTotal", status.getFileSize());
517                         sendJson(req, resp, json.toString());
518                         return;
519                 } catch (RpcException e) {
520                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
521                         return;
522                 } catch (JSONException e) {
523                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
524                         return;
525                 }
526         }
527
528         /**
529          * Server a POST request to create/modify a file or folder.
530          *
531          * @param req the HTTP request
532          * @param resp the HTTP response
533      * @exception IOException if an input/output error occurs
534          */
535         void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
536                 boolean authDeferred = getAuthDeferred(req);
537         if (!authDeferred && req.getParameterMap().size() > 1) {
538                 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
539                 return;
540         }
541         String path = getInnerPath(req, PATH_FILES);
542         path = path.endsWith("/")? path: path + '/';
543         path = URLDecoder.decode(path, "UTF-8");
544         // We only defer authenticating multipart POST requests.
545         if (authDeferred) {
546                         if (!ServletFileUpload.isMultipartContent(req)) {
547                         resp.sendError(HttpServletResponse.SC_FORBIDDEN);
548                         return;
549                 }
550                         handleMultipart(req, resp, path);
551                         return;
552                 }
553
554         String newName = req.getParameter(NEW_FOLDER_PARAMETER);
555         boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
556         boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
557         boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
558         String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
559         String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
560
561         if (newName != null)
562                         createFolder(req, resp, path, newName);
563                 else if (hasUpdateParam)
564                         updateResource(req, resp, path);
565                 else if (hasTrashParam)
566                         trashResource(req, resp, path);
567                 else if (hasRestoreParam)
568                         restoreResource(req, resp, path);
569                 else if (copyTo != null)
570                         copyResource(req, resp, path, copyTo);
571                 else if (moveTo != null)
572                         moveResource(req, resp, path, moveTo);
573                 else
574                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
575         }
576
577         /**
578          * A method for handling multipart POST requests for uploading
579          * files from browser-based JavaScript clients.
580          *
581          * @param request the HTTP request
582          * @param response the HTTP response
583          * @param path the resource path
584          * @throws IOException in case an error occurs writing to the
585          *              response stream
586          */
587         private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
588         if (logger.isDebugEnabled())
589                         logger.debug("Multipart POST for resource: " + path);
590
591         User owner = getOwner(request);
592         boolean exists = true;
593         Object resource = null;
594         FileHeaderDTO file = null;
595         try {
596                 resource = getService().getResourceAtPath(owner.getId(), path, false);
597         } catch (ObjectNotFoundException e) {
598             exists = false;
599         } catch (RpcException e) {
600                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
601                         return;
602                 }
603
604         if (exists)
605                         if (resource instanceof FileHeaderDTO)
606                         file = (FileHeaderDTO) resource;
607                         else {
608                         response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
609                         return;
610                 }
611
612         FolderDTO folder = null;
613         Object parent;
614         String parentPath = null;
615                 try {
616                         parentPath = getParentPath(path);
617                         parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
618                 } catch (ObjectNotFoundException e) {
619                 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
620                 return;
621                 } catch (RpcException e) {
622                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
623                         return;
624                 }
625         if (!(parent instanceof FolderDTO)) {
626                 response.sendError(HttpServletResponse.SC_CONFLICT);
627                 return;
628         }
629                 folder = (FolderDTO) parent;
630         String fileName = getLastElement(path);
631
632                 FileItemIterator iter;
633                 File uploadedFile = null;
634                 try {
635                         // Create a new file upload handler.
636                         ServletFileUpload upload = new ServletFileUpload();
637                         StatusProgressListener progressListener = new StatusProgressListener(getService());
638                         upload.setProgressListener(progressListener);
639                         iter = upload.getItemIterator(request);
640                         String dateParam = null;
641                         String auth = null;
642                         while (iter.hasNext()) {
643                                 FileItemStream item = iter.next();
644                                 String name = item.getFieldName();
645                                 InputStream stream = item.openStream();
646                                 if (item.isFormField()) {
647                                         final String value = Streams.asString(stream);
648                                         if (name.equals(DATE_PARAMETER))
649                                                 dateParam = value;
650                                         else if (name.equals(AUTHORIZATION_PARAMETER))
651                                                 auth = value;
652
653                                         if (logger.isDebugEnabled())
654                                                 logger.debug(name + ":" + value);
655                                 } else {
656                                         // Fetch the timestamp used to guard against replay attacks.
657                                 if (dateParam == null) {
658                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
659                                         return;
660                                 }
661
662                                 long timestamp;
663                                         try {
664                                                 timestamp = DateUtil.parseDate(dateParam).getTime();
665                                         } catch (DateParseException e) {
666                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
667                                         return;
668                                         }
669                                 if (!isTimeValid(timestamp)) {
670                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
671                                         return;
672                                 }
673
674                                         // Fetch the Authorization parameter and find the user specified in it.
675                                 if (auth == null) {
676                                         response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
677                                         return;
678                                 }
679                                         String[] authParts = auth.split(" ");
680                                         if (authParts.length != 2) {
681                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
682                                         return;
683                                 }
684                                         String username = authParts[0];
685                                         String signature = authParts[1];
686                                         User user = null;
687                                         try {
688                                                 user = getService().findUser(username);
689                                         } catch (RpcException e) {
690                                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
691                                                 return;
692                                         }
693                                         if (user == null) {
694                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
695                                         return;
696                                 }
697                                         request.setAttribute(USER_ATTRIBUTE, user);
698
699                                         // Validate the signature in the Authorization parameter.
700                                         String data = request.getMethod() + dateParam + URLEncoder.encode(request.getPathInfo(), "UTF-8");
701                                         if (!isSignatureValid(signature, user, data)) {
702                                         response.sendError(HttpServletResponse.SC_FORBIDDEN);
703                                         return;
704                                 }
705
706                                         progressListener.setUserId(user.getId());
707                                         progressListener.setFilename(fileName);
708                                         String contentType = item.getContentType();
709
710                                         try {
711                                                 uploadedFile = getService().uploadFile(stream, user.getId());
712                                         } catch (IOException ex) {
713                                                 throw new GSSIOException(ex, false);
714                                         }
715                                         if (file == null)
716                                                 getService().createFile(user.getId(), folder.getId(), fileName, contentType, uploadedFile);
717                                         else
718                                                 getService().updateFileContents(user.getId(), file.getId(), contentType, uploadedFile);
719                                         getService().removeFileUploadProgress(user.getId(), file.getName());
720                                 }
721                         }
722                 } catch (FileUploadException e) {
723                         String error = "Error while uploading file";
724                         logger.error(error, e);
725                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
726                 } catch (GSSIOException e) {
727                         if (uploadedFile != null && uploadedFile.exists())
728                                 uploadedFile.delete();
729                         String error = "Error while uploading file";
730                         if (e.logAsError())
731                                 logger.error(error, e);
732                         else
733                                 logger.debug(error, e);
734                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
735                 } catch (DuplicateNameException e) {
736                         if (uploadedFile != null && uploadedFile.exists())
737                                 uploadedFile.delete();
738                         String error = "The specified file name already exists in this folder";
739                         logger.error(error, e);
740                         response.sendError(HttpServletResponse.SC_CONFLICT, error);
741
742                 } catch (InsufficientPermissionsException e) {
743                         if (uploadedFile != null && uploadedFile.exists())
744                                 uploadedFile.delete();
745                         String error = "You don't have the necessary permissions";
746                         logger.error(error, e);
747                         response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
748
749                 } catch (QuotaExceededException e) {
750                         if (uploadedFile != null && uploadedFile.exists())
751                                 uploadedFile.delete();
752                         String error = "Not enough free space available";
753                         if (logger.isDebugEnabled())
754                                 logger.debug(error, e);
755                         response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
756
757                 } catch (ObjectNotFoundException e) {
758                         if (uploadedFile != null && uploadedFile.exists())
759                                 uploadedFile.delete();
760                         String error = "A specified object was not found";
761                         logger.error(error, e);
762                         response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
763                 } catch (RpcException e) {
764                         if (uploadedFile != null && uploadedFile.exists())
765                                 uploadedFile.delete();
766                         String error = "An error occurred while communicating with the service";
767                         logger.error(error, e);
768                         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
769                 }
770         }
771
772         /**
773          * Move the resource in the specified path to the specified destination.
774          *
775          * @param req the HTTP request
776          * @param resp the HTTP response
777          * @param path the path of the resource
778          * @param moveTo the destination of the move procedure
779          * @throws IOException if an input/output error occurs
780          */
781         private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
782                 User user = getUser(req);
783                 User owner = getOwner(req);
784                 Object resource = null;
785                 try {
786                         resource = getService().getResourceAtPath(owner.getId(), path, true);
787                 } catch (ObjectNotFoundException e) {
788                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
789                         return;
790                 } catch (RpcException e) {
791                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
792                         return;
793                 }
794
795         String destination = null;
796         User destOwner = null;
797                 boolean exists = true;
798                 try {
799                         destination = getDestinationPath(req, encodePath(moveTo));
800                         destination = URLDecoder.decode(destination, "UTF-8");
801                         destOwner = getDestinationOwner(req);
802                         getService().getResourceAtPath(destOwner.getId(), destination, true);
803                 } catch (ObjectNotFoundException e) {
804                         exists = false;
805                 } catch (URISyntaxException e) {
806                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
807                         return;
808                 } catch (RpcException e) {
809                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
810                         return;
811                 }
812                 if (exists) {
813                         resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
814                         return;
815                 }
816
817                 try {
818                         if (resource instanceof FolderDTO) {
819                                 FolderDTO folder = (FolderDTO) resource;
820                                 getService().moveFolderToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
821                         } else {
822                                 FileHeaderDTO file = (FileHeaderDTO) resource;
823                                 getService().moveFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
824                         }
825                 } catch (InsufficientPermissionsException e) {
826                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
827                 } catch (ObjectNotFoundException e) {
828                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
829                 } catch (RpcException e) {
830                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
831                 } catch (DuplicateNameException e) {
832                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
833                 } catch (GSSIOException e) {
834                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
835                 } catch (QuotaExceededException e) {
836                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
837                 }
838         }
839
840         /**
841          * Copy the resource in the specified path to the specified destination.
842          *
843          * @param req the HTTP request
844          * @param resp the HTTP response
845          * @param path the path of the resource
846          * @param copyTo the destination of the copy procedure
847          * @throws IOException if an input/output error occurs
848          */
849         private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
850                 User user = getUser(req);
851                 User owner = getOwner(req);
852                 Object resource = null;
853                 try {
854                         resource = getService().getResourceAtPath(owner.getId(), path, true);
855                 } catch (ObjectNotFoundException e) {
856                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
857                         return;
858                 } catch (RpcException e) {
859                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
860                         return;
861                 }
862
863         String destination = null;
864         User destOwner = null;
865                 boolean exists = true;
866                 try {
867                         destination = getDestinationPath(req, encodePath(copyTo));
868                         destination = URLDecoder.decode(destination, "UTF-8");
869                         destOwner = getDestinationOwner(req);
870                         getService().getResourceAtPath(destOwner.getId(), destination, true);
871                 } catch (ObjectNotFoundException e) {
872                         exists = false;
873                 } catch (URISyntaxException e) {
874                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
875                         return;
876                 } catch (RpcException e) {
877                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
878                         return;
879                 }
880                 if (exists) {
881                         resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
882                         return;
883                 }
884
885                 try {
886                         if (resource instanceof FolderDTO) {
887                                 FolderDTO folder = (FolderDTO) resource;
888                                 getService().copyFolderStructureToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
889                         } else {
890                                 FileHeaderDTO file = (FileHeaderDTO) resource;
891                                 getService().copyFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
892                         }
893                 } catch (InsufficientPermissionsException e) {
894                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
895                 } catch (ObjectNotFoundException e) {
896                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
897                 } catch (RpcException e) {
898                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
899                 } catch (DuplicateNameException e) {
900                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
901                 } catch (GSSIOException e) {
902                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
903                 } catch (QuotaExceededException e) {
904                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
905                 }
906         }
907
908         private String encodePath(String path) throws UnsupportedEncodingException{
909                 StringTokenizer str = new StringTokenizer(path, "/:", true);
910                 String result = new String();
911                 while(str.hasMoreTokens()){
912                         String token = str.nextToken();
913                         if(!token.equals("/") && !token.equals(":"))
914                                 token = URLEncoder.encode(token,"UTF-8");
915                         result = result + token;
916                 }
917                 return result;
918         }
919         /**
920          * A helper method that extracts the relative resource path,
921          * after removing the 'files' namespace.
922          *
923          * @param req the HTTP request
924          * @param path the specified path
925          * @return the path relative to the root folder
926          * @throws URISyntaxException
927          * @throws RpcException in case an error occurs while communicating
928          *                                              with the backend
929          */
930         private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException {
931                 URI uri = new URI(path);
932                 String dest = uri.getPath();
933                 // Remove the context path from the destination URI.
934                 String contextPath = req.getContextPath();
935                 if (!dest.startsWith(contextPath))
936                         throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
937                 dest = dest.substring(contextPath.length());
938                 // Remove the servlet path from the destination URI.
939                 String servletPath = req.getServletPath();
940                 if (!dest.startsWith(servletPath))
941                         throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
942                 dest = dest.substring(servletPath.length());
943         // Strip the username part
944                 if (dest.length() < 2)
945                         throw new URISyntaxException(dest, "No username in the destination URI");
946                 int slash = dest.substring(1).indexOf('/');
947                 if (slash == -1)
948                         throw new URISyntaxException(dest, "No username in the destination URI");
949                 String owner = dest.substring(1, slash + 1);
950                 User o;
951                 o = getService().findUser(owner);
952                 if (o == null)
953                         throw new URISyntaxException(dest, "User " + owner + " not found");
954
955                 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
956                 dest = dest.substring(slash + 1);
957
958                 // Chop the resource namespace part
959                 dest = dest.substring(RequestHandler.PATH_FILES.length());
960
961         dest = dest.endsWith("/")? dest: dest + '/';
962                 return dest;
963         }
964
965         /**
966          * Move the resource in the specified path to the trash bin.
967          *
968          * @param req the HTTP request
969          * @param resp the HTTP response
970          * @param path the path of the resource
971          * @throws IOException if an input/output error occurs
972          */
973         private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
974                 User user = getUser(req);
975                 User owner = getOwner(req);
976                 Object resource = null;
977                 try {
978                         resource = getService().getResourceAtPath(owner.getId(), path, true);
979                 } catch (ObjectNotFoundException e) {
980                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
981                         return;
982                 } catch (RpcException e) {
983                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
984                         return;
985                 }
986
987                 try {
988                         if (resource instanceof FolderDTO) {
989                                 FolderDTO folder = (FolderDTO) resource;
990                                 getService().moveFolderToTrash(user.getId(), folder.getId());
991                         } else {
992                                 FileHeaderDTO file = (FileHeaderDTO) resource;
993                                 getService().moveFileToTrash(user.getId(), file.getId());
994                         }
995                 } catch (InsufficientPermissionsException e) {
996                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
997                 } catch (ObjectNotFoundException e) {
998                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
999                 } catch (RpcException e) {
1000                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1001                 }
1002         }
1003
1004         /**
1005          * Restore the resource in the specified path from the trash bin.
1006          *
1007          * @param req the HTTP request
1008          * @param resp the HTTP response
1009          * @param path the path of the resource
1010          * @throws IOException if an input/output error occurs
1011          */
1012         private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1013                 User user = getUser(req);
1014                 User owner = getOwner(req);
1015                 Object resource = null;
1016                 try {
1017                         resource = getService().getResourceAtPath(owner.getId(), path, false);
1018                 } catch (ObjectNotFoundException e) {
1019                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1020                         return;
1021                 } catch (RpcException e) {
1022                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1023                         return;
1024                 }
1025
1026                 try {
1027                         if (resource instanceof FolderDTO) {
1028                                 FolderDTO folder = (FolderDTO) resource;
1029                                 getService().removeFolderFromTrash(user.getId(), folder.getId());
1030                         } else {
1031                                 FileHeaderDTO file = (FileHeaderDTO) resource;
1032                                 getService().removeFileFromTrash(user.getId(), file.getId());
1033                         }
1034                 } catch (InsufficientPermissionsException e) {
1035                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1036                 } catch (ObjectNotFoundException e) {
1037                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1038                 } catch (RpcException e) {
1039                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1040                 }
1041         }
1042
1043         /**
1044          * Update the resource in the specified path.
1045          *
1046          * @param req the HTTP request
1047          * @param resp the HTTP response
1048          * @param path the path of the resource
1049          * @throws IOException if an input/output error occurs
1050          */
1051         private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1052                 User user = getUser(req);
1053                 User owner = getOwner(req);
1054                 Object resource = null;
1055                 try {
1056                         resource = getService().getResourceAtPath(owner.getId(), path, true);
1057                 } catch (ObjectNotFoundException e) {
1058                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1059                         return;
1060                 } catch (RpcException e) {
1061                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1062                         return;
1063                 }
1064                 //use utf-8 encoding for reading request
1065                 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1066                 StringBuffer input = new StringBuffer();
1067                 String line = null;
1068                 JSONObject json = null;
1069                 while ((line = reader.readLine()) != null)
1070                         input.append(line);
1071                 reader.close();
1072                 try {
1073                         json = new JSONObject(input.toString());
1074                         if (logger.isDebugEnabled())
1075                                 logger.debug("JSON update: " + json);
1076                         if (resource instanceof FolderDTO) {
1077                                 FolderDTO folder = (FolderDTO) resource;
1078                                 String name = json.optString("name");
1079                                 if (!name.isEmpty()){
1080                                         getService().modifyFolder(user.getId(), folder.getId(), name);
1081                                         FolderDTO folderUpdated = getService().getFolder(user.getId(), folder.getId());
1082                                         String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1083                                         String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1084                                         parentUrl = parentUrl.replaceAll(fpath, "");
1085                                         if(!parentUrl.endsWith("/"))
1086                                                 parentUrl = parentUrl+"/";
1087                                         parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
1088                                         resp.getWriter().println(parentUrl);
1089                                 }
1090
1091                                 JSONArray permissions = json.optJSONArray("permissions");
1092                                 if (permissions != null) {
1093                                         Set<PermissionDTO> perms = parsePermissions(user, permissions);
1094                                         getService().setFolderPermissions(user.getId(), folder.getId(), perms);
1095                                 }
1096                         } else {
1097                                 FileHeaderDTO file = (FileHeaderDTO) resource;
1098                                 String name = null;
1099                                 if (json.opt("name") != null)
1100                                         name = json.optString("name");
1101                                 JSONArray tagset = json.optJSONArray("tags");
1102                                 String tags = null;
1103                                 StringBuffer t = new StringBuffer();
1104                                 if (tagset != null) {
1105                                         for (int i = 0; i < tagset.length(); i++)
1106                                                 t.append(tagset.getString(i) + ',');
1107                                         tags = t.toString();
1108                                 }
1109                                 if (name != null || tags != null)
1110                                         getService().updateFile(user.getId(), file.getId(), name, tags);
1111
1112                                 JSONArray permissions = json.optJSONArray("permissions");
1113                                 Set<PermissionDTO> perms = null;
1114                                 if (permissions != null)
1115                                         perms = parsePermissions(user, permissions);
1116                                 Boolean readForAll = null;
1117                                 if (json.opt("readForAll") != null)
1118                                         readForAll = json.optBoolean("readForAll");
1119                                 if (perms != null || readForAll != null)
1120                                         getService().setFilePermissions(user.getId(), file.getId(), readForAll, perms);
1121
1122                                 if (json.opt("versioned") != null) {
1123                                         boolean versioned = json.getBoolean("versioned");
1124                                         getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
1125                                 }
1126                         }
1127                 } catch (JSONException e) {
1128                         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1129                 } catch (InsufficientPermissionsException e) {
1130                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1131                 } catch (ObjectNotFoundException e) {
1132                         resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1133                 } catch (DuplicateNameException e) {
1134                         resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1135                 } catch (RpcException e) {
1136                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1137                 }
1138         }
1139
1140         /**
1141          * Helper method to convert a JSON array of permissions into a set of
1142          * PermissionDTO objects.
1143          *
1144          * @param user the current user
1145          * @param permissions the JSON array to parse
1146          * @return the parsed set of permissions
1147          * @throws JSONException if there was an error parsing the JSON object
1148          * @throws RpcException if there was an error communicating with the EJB
1149          * @throws ObjectNotFoundException if the user could not be found
1150          */
1151         private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1152                         throws JSONException, RpcException, ObjectNotFoundException {
1153                 if (permissions == null)
1154                         return null;
1155                 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1156                 for (int i = 0; i < permissions.length(); i++) {
1157                         JSONObject j = permissions.getJSONObject(i);
1158                         PermissionDTO perm = new PermissionDTO();
1159                         perm.setModifyACL(j.optBoolean("modifyACL"));
1160                         perm.setRead(j.optBoolean("read"));
1161                         perm.setWrite(j.optBoolean("write"));
1162                         String permUser = j.optString("user");
1163                         if (!permUser.isEmpty()) {
1164                                 User u = getService().findUser(permUser);
1165                                 if (u == null)
1166                                         throw new ObjectNotFoundException("User " + permUser + " not found");
1167                                 perm.setUser(u.getDTO());
1168                         }
1169                         String permGroup = j.optString("group");
1170                         if (!permGroup.isEmpty()) {
1171                                 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1172                                 perm.setGroup(g);
1173                         }
1174                         if (permUser.isEmpty() && permGroup.isEmpty() ||
1175                                                 permUser.isEmpty() && permGroup.isEmpty())
1176                                 throw new JSONException("A permission must correspond to either a user or a group");
1177                         perms.add(perm);
1178                 }
1179                 return perms;
1180         }
1181
1182         /**
1183          * Creates a new folder with the specified name under the folder in the provided path.
1184          *
1185          * @param req the HTTP request
1186          * @param resp the HTTP response
1187          * @param path the parent folder path
1188          * @param folderName the name of the new folder
1189          * @throws IOException if an input/output error occurs
1190          */
1191         private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, String folderName) throws IOException {
1192                 if (logger.isDebugEnabled())
1193                         logger.debug("Creating folder " + folderName + " in '" + path);
1194
1195         User user = getUser(req);
1196         User owner = getOwner(req);
1197         boolean exists = true;
1198         try {
1199                 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1200         } catch (ObjectNotFoundException e) {
1201             exists = false;
1202         } catch (RpcException e) {
1203                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1204                         return;
1205                 }
1206
1207         if (exists) {
1208             resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1209                                 ", " + METHOD_HEAD);
1210             resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1211             return;
1212         }
1213
1214                 Object parent;
1215                 try {
1216                         parent = getService().getResourceAtPath(owner.getId(), path, true);
1217                 } catch (ObjectNotFoundException e) {
1218                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1219                         return;
1220                 } catch (RpcException e) {
1221                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1222                         return;
1223                 }
1224                 try {
1225                         if (parent instanceof FolderDTO) {
1226                                 FolderDTO folder = (FolderDTO) parent;
1227                                 getService().createFolder(user.getId(), folder.getId(), folderName);
1228                         String newResource = getContextPath(req, true) + folderName;
1229                         resp.setHeader("Location", newResource);
1230                         resp.setContentType("text/plain");
1231                     PrintWriter out = resp.getWriter();
1232                     out.println(newResource);
1233                         } else {
1234                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1235                         return;
1236                         }
1237                 } catch (DuplicateNameException e) {
1238                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1239                 return;
1240                 } catch (InsufficientPermissionsException e) {
1241                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1242                 return;
1243                 } catch (ObjectNotFoundException e) {
1244                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1245                         return;
1246                 } catch (RpcException e) {
1247                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1248                         return;
1249                 }
1250         resp.setStatus(HttpServletResponse.SC_CREATED);
1251         }
1252
1253         /**
1254          * @param req
1255          * @param resp
1256          * @throws IOException
1257          * @throws FileNotFoundException
1258          */
1259         void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1260         String path = getInnerPath(req, PATH_FILES);
1261         if (logger.isDebugEnabled())
1262                         logger.debug("Updating resource: " + path);
1263
1264         User user = getUser(req);
1265         User owner = getOwner(req);
1266         boolean exists = true;
1267         Object resource = null;
1268         FileHeaderDTO file = null;
1269         try {
1270                 resource = getService().getResourceAtPath(owner.getId(), path, false);
1271         } catch (ObjectNotFoundException e) {
1272             exists = false;
1273         } catch (RpcException e) {
1274                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1275                         return;
1276                 }
1277
1278         if (exists)
1279                         if (resource instanceof FileHeaderDTO)
1280                         file = (FileHeaderDTO) resource;
1281                         else {
1282                         resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1283                         return;
1284                 }
1285         boolean result = true;
1286
1287         // Temporary content file used to support partial PUT.
1288         File contentFile = null;
1289
1290         Range range = parseContentRange(req, resp);
1291
1292         InputStream resourceInputStream = null;
1293
1294         // Append data specified in ranges to existing content for this
1295         // resource - create a temporary file on the local filesystem to
1296         // perform this operation.
1297         // Assume just one range is specified for now
1298         if (range != null) {
1299             try {
1300                                 contentFile = executePartialPut(req, range, path);
1301                         } catch (RpcException e) {
1302                                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1303                                 return;
1304                         } catch (ObjectNotFoundException e) {
1305                                 resp.sendError(HttpServletResponse.SC_CONFLICT);
1306                         return;
1307                         } catch (InsufficientPermissionsException e) {
1308                                 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1309                         return;
1310                         }
1311             resourceInputStream = new FileInputStream(contentFile);
1312         } else
1313                         resourceInputStream = req.getInputStream();
1314
1315         try {
1316                 FolderDTO folder = null;
1317                 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1318                 if (!(parent instanceof FolderDTO)) {
1319                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1320                         return;
1321                 }
1322                 folder = (FolderDTO) parent;
1323                 String name = getLastElement(path);
1324                 String mimeType = context.getMimeType(name);
1325             // FIXME: Add attributes
1326             if (exists)
1327                                 getService().updateFileContents(user.getId(), file.getId(), mimeType, resourceInputStream);
1328                         else
1329                         getService().createFile(user.getId(), folder.getId(), name, mimeType, resourceInputStream);
1330                         getService().removeFileUploadProgress(user.getId(), file.getName());
1331         } catch(ObjectNotFoundException e) {
1332             result = false;
1333         } catch (RpcException e) {
1334                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1335                         return;
1336         } catch (IOException e) {
1337                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1338                         return;
1339                 } catch (GSSIOException e) {
1340                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1341                         return;
1342                 } catch (DuplicateNameException e) {
1343                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1344                 return;
1345                 } catch (InsufficientPermissionsException e) {
1346                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1347                 return;
1348                 } catch (QuotaExceededException e) {
1349                         resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1350                 return;
1351                 }
1352
1353         if (result) {
1354             if (exists)
1355                                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1356                         else
1357                                 resp.setStatus(HttpServletResponse.SC_CREATED);
1358         } else
1359                         resp.sendError(HttpServletResponse.SC_CONFLICT);
1360         }
1361
1362     /**
1363      * Delete a resource.
1364      *
1365      * @param req The servlet request we are processing
1366      * @param resp The servlet response we are processing
1367          * @throws IOException if the response cannot be sent
1368      */
1369     void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1370         String path = getInnerPath(req, PATH_FILES);
1371         if (logger.isDebugEnabled())
1372                         logger.debug("Deleting resource '" + path);
1373         path = URLDecoder.decode(path, "UTF-8");
1374         User user = getUser(req);
1375         User owner = getOwner(req);
1376         boolean exists = true;
1377         Object object = null;
1378         try {
1379                 object = getService().getResourceAtPath(owner.getId(), path, false);
1380         } catch (ObjectNotFoundException e) {
1381                 exists = false;
1382         } catch (RpcException e) {
1383                 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1384                         return;
1385                 }
1386
1387         if (!exists) {
1388                 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1389                 return;
1390         }
1391
1392         FolderDTO folder = null;
1393         FileHeaderDTO file = null;
1394         if (object instanceof FolderDTO)
1395                 folder = (FolderDTO) object;
1396         else
1397                 file = (FileHeaderDTO) object;
1398
1399         if (file != null)
1400                         try {
1401                                 getService().deleteFile(user.getId(), file.getId());
1402                 } catch (InsufficientPermissionsException e) {
1403                         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1404                                 return;
1405                 } catch (ObjectNotFoundException e) {
1406                         // Although we had already found the object, it was
1407                         // probably deleted from another thread.
1408                         resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1409                         return;
1410                 } catch (RpcException e) {
1411                         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1412                         return;
1413                 }
1414                 else if (folder != null)
1415                         try {
1416                         getService().deleteFolder(user.getId(), folder.getId());
1417                 } catch (InsufficientPermissionsException e) {
1418                         resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1419                         return;
1420                 } catch (ObjectNotFoundException e) {
1421                         resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1422                         return;
1423                 } catch (RpcException e) {
1424                         resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1425                         return;
1426                 }
1427                 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1428         return;
1429     }
1430
1431         /**
1432      * Return an InputStream to a JSON representation of the contents
1433      * of this directory.
1434      *
1435          * @param user the user that made the request
1436      * @param folder the specified directory
1437      * @param folderUrl the URL of the folder
1438      * @return an input stream with the rendered contents
1439          * @throws IOException if the response cannot be sent
1440      * @throws ServletException
1441          * @throws InsufficientPermissionsException if the user does not have
1442          *                      the necessary privileges to read the directory
1443      */
1444     private InputStream renderJson(User user, FolderDTO folder, String folderUrl) throws IOException,
1445                 ServletException, InsufficientPermissionsException {
1446         JSONObject json = new JSONObject();
1447         try {
1448                         json.put("name", folder.getName()).
1449                                         put("owner", folder.getOwner().getUsername()).
1450                                         put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1451                                         put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1452                                         put("deleted", folder.isDeleted());
1453                         if (folder.getParent() != null)
1454                                 json.put("parent", folder.getParent().getURI());
1455                         if (folder.getAuditInfo().getModifiedBy() != null)
1456                                 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1457                                                 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1458                 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1459                 for (FolderDTO f: folder.getSubfolders())
1460                                 if (!f.isDeleted()) {
1461                                         JSONObject j = new JSONObject();
1462                                         j.put("name", f.getName()).
1463                                                 put("uri", folderUrl + URLEncoder.encode(f.getName(), "UTF-8"));
1464                                         subfolders.add(j);
1465                                 }
1466                 json.put("folders", subfolders);
1467                 List<JSONObject> files = new ArrayList<JSONObject>();
1468                 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId());
1469                 for (FileHeaderDTO f: fileHeaders) {
1470                         JSONObject j = new JSONObject();
1471                                 j.put("name", f.getName()).
1472                                         put("owner", f.getOwner().getUsername()).
1473                                         put("deleted", f.isDeleted()).
1474                                         put("version", f.getVersion()).
1475                                         put("size", f.getFileSize()).
1476                                         put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1477                                         put("uri", folderUrl + URLEncoder.encode(f.getName(), "UTF-8"));
1478                                 files.add(j);
1479                 }
1480                 json.put("files", files);
1481                 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1482                 json.put("permissions", renderJson(perms));
1483                 } catch (JSONException e) {
1484                         throw new ServletException(e);
1485                 } catch (ObjectNotFoundException e) {
1486                         throw new ServletException(e);
1487                 } catch (RpcException e) {
1488                         throw new ServletException(e);
1489                 }
1490
1491         // Prepare a writer to a buffered area
1492         ByteArrayOutputStream stream = new ByteArrayOutputStream();
1493         OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1494         PrintWriter writer = new PrintWriter(osWriter);
1495
1496         // Return an input stream to the underlying bytes
1497         writer.write(json.toString());
1498         writer.flush();
1499         return new ByteArrayInputStream(stream.toByteArray());
1500     }
1501
1502         /**
1503      * Return a String with a JSON representation of the metadata
1504      * of the specified file. If an old file body is provided, then
1505      * the metadata of that particular version will be returned.
1506      *
1507          * @param user the user that made the request
1508      * @param file the specified file header
1509      * @param oldBody the version number
1510      * @return the JSON-encoded file
1511      * @throws ServletException
1512          * @throws InsufficientPermissionsException if the user does not have
1513          *                      the necessary privileges to read the directory
1514      */
1515     private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1516                 throws ServletException, InsufficientPermissionsException {
1517         JSONObject json = new JSONObject();
1518         try {
1519                 //need to encode file name in order to properly display it in gwt
1520                         json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1521                                         put("owner", file.getOwner().getUsername()).
1522                                         put("versioned", file.isVersioned()).
1523                                         put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1524                                         put("readForAll", file.isReadForAll()).
1525                                         put("tags", file.getTags()).
1526                                         put("folder", file.getFolder().getURI()).
1527                                         put("deleted", file.isDeleted());
1528                         if (oldBody != null)
1529                                 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1530                                                 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1531                                                 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1532                                                 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime());
1533                         else
1534                                 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1535                                                 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1536                                                 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1537                                                 put("modificationDate", file.getAuditInfo().getModificationDate().getTime());
1538                 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1539                 json.put("permissions", renderJson(perms));
1540                 } catch (JSONException e) {
1541                         throw new ServletException(e);
1542                 } catch (ObjectNotFoundException e) {
1543                         throw new ServletException(e);
1544                 } catch (RpcException e) {
1545                         throw new ServletException(e);
1546                 } catch (UnsupportedEncodingException e) {
1547                         throw new ServletException(e);
1548                 }
1549
1550         return json.toString();
1551     }
1552
1553         /**
1554          * Return a String with a JSON representation of the
1555          * specified set of permissions.
1556      *
1557          * @param permissions the set of permissions
1558          * @return the JSON-encoded object
1559          * @throws JSONException
1560          */
1561         private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException {
1562                 JSONArray perms = new JSONArray();
1563                 for (PermissionDTO p: permissions) {
1564                         JSONObject permission = new JSONObject();
1565                         permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
1566                         if (p.getUser() != null)
1567                                 permission.put("user", p.getUser().getUsername());
1568                         if (p.getGroup() != null)
1569                                 permission.put("group", p.getGroup().getName());
1570                         perms.put(permission);
1571                 }
1572                 return perms;
1573         }
1574
1575         /**
1576          * Retrieves the user who owns the destination namespace, for a
1577          * copy or move request.
1578          *
1579          * @param req the HTTP request
1580          * @return the owner of the namespace
1581          */
1582         protected User getDestinationOwner(HttpServletRequest req) {
1583                 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
1584         }
1585
1586         /**
1587          * A helper inner class for updating the progress status of a file upload.
1588          *
1589          * @author kman
1590          */
1591         public static class StatusProgressListener implements ProgressListener {
1592                 private int percentLogged = 0;
1593                 private long bytesTransferred = 0;
1594
1595                 private long fileSize = -100;
1596
1597                 private long tenKBRead = -1;
1598
1599                 private Long userId;
1600
1601                 private String filename;
1602
1603                 private ExternalAPI service;
1604
1605                 public StatusProgressListener(ExternalAPI aService) {
1606                         service = aService;
1607                 }
1608
1609                 /**
1610                  * Modify the userId.
1611                  *
1612                  * @param aUserId the userId to set
1613                  */
1614                 public void setUserId(Long aUserId) {
1615                         userId = aUserId;
1616                 }
1617
1618                 /**
1619                  * Modify the filename.
1620                  *
1621                  * @param aFilename the filename to set
1622                  */
1623                 public void setFilename(String aFilename) {
1624                         filename = aFilename;
1625                 }
1626
1627                 public void update(long bytesRead, long contentLength, int items) {
1628                         //monitoring per percent of bytes uploaded
1629                         bytesTransferred = bytesRead;
1630                         if (fileSize != contentLength)
1631                                 fileSize = contentLength;
1632                         int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
1633
1634                         if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
1635                                 if (percent != percentLogged){
1636                                         percentLogged = percent;
1637                                         try {
1638                                                 if (userId != null && filename != null)
1639                                                         service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
1640                                         } catch (ObjectNotFoundException e) {
1641                                                 // Swallow the exception since it is going to be caught
1642                                                 // by previously called methods
1643                                         }
1644                                 }
1645                 }
1646         }
1647 }