2 * Copyright 2008, 2009 Electronic Business Systems Ltd.
4 * This file is part of GSS.
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.
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.
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/>.
19 package gr.ebs.gss.server.rest;
21 import gr.ebs.gss.client.exceptions.DuplicateNameException;
22 import gr.ebs.gss.client.exceptions.GSSIOException;
23 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
24 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
25 import gr.ebs.gss.client.exceptions.QuotaExceededException;
26 import gr.ebs.gss.client.exceptions.RpcException;
27 import gr.ebs.gss.server.domain.FileUploadStatus;
28 import gr.ebs.gss.server.domain.User;
29 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
30 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
31 import gr.ebs.gss.server.domain.dto.FolderDTO;
32 import gr.ebs.gss.server.domain.dto.GroupDTO;
33 import gr.ebs.gss.server.domain.dto.PermissionDTO;
34 import gr.ebs.gss.server.ejb.ExternalAPI;
35 import gr.ebs.gss.server.ejb.TransactionHelper;
36 import gr.ebs.gss.server.webdav.Range;
38 import java.io.BufferedReader;
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.OutputStreamWriter;
48 import java.io.PrintWriter;
49 import java.io.UnsupportedEncodingException;
51 import java.net.URISyntaxException;
52 import java.net.URLDecoder;
53 import java.net.URLEncoder;
54 import java.util.ArrayList;
55 import java.util.Collection;
56 import java.util.Date;
57 import java.util.HashSet;
58 import java.util.List;
60 import java.util.StringTokenizer;
61 import java.util.concurrent.Callable;
63 import javax.servlet.ServletContext;
64 import javax.servlet.ServletException;
65 import javax.servlet.ServletOutputStream;
66 import javax.servlet.http.HttpServletRequest;
67 import javax.servlet.http.HttpServletResponse;
69 import org.apache.commons.fileupload.FileItemIterator;
70 import org.apache.commons.fileupload.FileItemStream;
71 import org.apache.commons.fileupload.FileUploadException;
72 import org.apache.commons.fileupload.ProgressListener;
73 import org.apache.commons.fileupload.servlet.ServletFileUpload;
74 import org.apache.commons.fileupload.util.Streams;
75 import org.apache.commons.httpclient.util.DateParseException;
76 import org.apache.commons.httpclient.util.DateUtil;
77 import org.apache.commons.logging.Log;
78 import org.apache.commons.logging.LogFactory;
79 import org.json.JSONArray;
80 import org.json.JSONException;
81 import org.json.JSONObject;
85 * A class that handles operations on the 'files' namespace.
89 public class FilesHandler extends RequestHandler {
91 * The request parameter name for fetching a different version.
93 private static final String VERSION_PARAM = "version";
96 * The request attribute containing the owner of the destination URI
97 * in a copy or move request.
99 private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
101 private static final int TRACK_PROGRESS_PERCENT = 5;
104 * The form parameter name that contains the signature in a browser POST upload.
106 private static final String AUTHORIZATION_PARAMETER = "Authorization";
109 * The form parameter name that contains the date in a browser POST upload.
111 private static final String DATE_PARAMETER = "Date";
114 * The request parameter name for making an upload progress request.
116 private static final String PROGRESS_PARAMETER = "progress";
119 * The request parameter name for restoring a previous version of a file.
121 private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
126 private static Log logger = LogFactory.getLog(FilesHandler.class);
129 * The servlet context provided by the call site.
131 private ServletContext context;
134 * @param servletContext
136 public FilesHandler(ServletContext servletContext) {
137 context = servletContext;
140 private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
142 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
144 public Void call() throws Exception {
145 getService().updateAccounting(user, date, bandwidthDiff);
149 } catch (RuntimeException e) {
151 } catch (Exception e) {
152 // updateAccounting() doesn't throw any checked exceptions
158 * Serve the specified resource, optionally including the data content.
160 * @param req The servlet request we are processing
161 * @param resp The servlet response we are creating
162 * @param content Should the content be included?
164 * @exception IOException if an input/output error occurs
165 * @exception ServletException if a servlet-specified error occurs
166 * @throws RpcException
167 * @throws InsufficientPermissionsException
168 * @throws ObjectNotFoundException
171 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
172 throws IOException, ServletException {
173 boolean authDeferred = getAuthDeferred(req);
174 String path = getInnerPath(req, PATH_FILES);
178 path = URLDecoder.decode(path, "UTF-8");
179 } catch (IllegalArgumentException e) {
180 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
183 String progress = req.getParameter(PROGRESS_PARAMETER);
185 if (logger.isDebugEnabled())
187 logger.debug("Serving resource '" + path + "' headers and data");
189 logger.debug("Serving resource '" + path + "' headers only");
191 User user = getUser(req);
192 User owner = getOwner(req);
193 if (user == null) user = owner;
194 boolean exists = true;
195 Object resource = null;
196 FileHeaderDTO file = null;
197 FolderDTO folder = null;
199 resource = getService().getResourceAtPath(owner.getId(), path, false);
200 } catch (ObjectNotFoundException e) {
202 } catch (RpcException e) {
203 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
209 // We do not want to leak information if the request
210 // was not authenticated.
211 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
214 // A request for upload progress.
215 if (progress != null && content) {
216 serveProgress(req, resp, progress, user, null);
220 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
224 if (resource instanceof FolderDTO)
225 folder = (FolderDTO) resource;
227 file = (FileHeaderDTO) resource;
229 // Now it's time to perform the deferred authentication check.
230 // Since regular signature checking was already performed,
231 // we need to check the read-all flag or the signature-in-parameters.
233 if (file != null && !file.isReadForAll() && content) {
234 // Check for GET with the signature in the request parameters.
235 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
236 String dateParam = req.getParameter(DATE_PARAMETER);
237 if (auth == null || dateParam == null) {
238 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
244 timestamp = DateUtil.parseDate(dateParam).getTime();
245 } catch (DateParseException e) {
246 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
249 if (!isTimeValid(timestamp)) {
250 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
254 // Fetch the Authorization parameter and find the user specified in it.
255 String[] authParts = auth.split(" ");
256 if (authParts.length != 2) {
257 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
260 String username = authParts[0];
261 String signature = authParts[1];
264 user = getService().findUser(username);
265 } catch (RpcException e) {
266 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
270 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
273 req.setAttribute(USER_ATTRIBUTE, user);
275 // Remove the servlet path from the request URI.
276 String p = req.getRequestURI();
277 String servletPath = req.getContextPath() + req.getServletPath();
278 p = p.substring(servletPath.length());
279 // Validate the signature in the Authorization parameter.
280 String data = req.getMethod() + dateParam + p;
281 if (!isSignatureValid(signature, user, data)) {
282 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
285 } else if (file != null && !file.isReadForAll() || file == null) {
286 // Check for a read-for-all file request.
287 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
291 // If the resource is not a collection, and the resource path
292 // ends with "/" or "\", return NOT FOUND.
294 if (path.endsWith("/") || path.endsWith("\\")) {
295 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
299 // Workaround for IE's broken caching behavior.
301 resp.setHeader("Expires", "-1");
303 // A request for upload progress.
304 if (progress != null && content) {
306 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
309 serveProgress(req, resp, progress, user, file);
313 // Fetch the version to retrieve, if specified.
314 String verStr = req.getParameter(VERSION_PARAM);
316 FileBodyDTO oldBody = null;
317 if (verStr != null && file != null)
319 version = Integer.valueOf(verStr);
320 } catch (NumberFormatException e) {
321 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
326 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
327 } catch (RpcException e) {
328 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
330 } catch (ObjectNotFoundException e) {
331 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
333 } catch (InsufficientPermissionsException e) {
334 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
338 // Check if the conditions specified in the optional If headers are
339 // satisfied. Doing this for folders would require recursive checking
340 // for all of their children, which in turn would defy the purpose of
343 // Checking If headers.
344 if (!checkIfHeaders(req, resp, file, oldBody))
347 // Find content type.
348 String contentType = null;
350 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
351 if (contentType == null) {
352 contentType = context.getMimeType(file.getName());
353 file.setMimeType(contentType);
356 contentType = "application/json;charset=UTF-8";
358 ArrayList ranges = null;
359 long contentLength = -1L;
362 // Parse range specifier.
363 ranges = parseRange(req, resp, file, oldBody);
365 resp.setHeader("ETag", getETag(file, oldBody));
366 // Last-Modified header.
367 String lastModified = oldBody == null ?
368 getLastModifiedHttp(file.getAuditInfo()) :
369 getLastModifiedHttp(oldBody.getAuditInfo());
370 resp.setHeader("Last-Modified", lastModified);
371 // X-GSS-Metadata header.
373 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
374 } catch (InsufficientPermissionsException e) {
375 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
378 // Get content length.
379 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
380 // Special case for zero length files, which would cause a
381 // (silent) ISE when setting the output buffer size.
382 if (contentLength == 0L)
385 // Set the folder X-GSS-Metadata header.
387 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
388 } catch (InsufficientPermissionsException e) {
389 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
393 ServletOutputStream ostream = null;
394 PrintWriter writer = null;
398 ostream = resp.getOutputStream();
399 } catch (IllegalStateException e) {
400 // If it fails, we try to get a Writer instead if we're
401 // trying to serve a text file
402 if ( contentType == null
403 || contentType.startsWith("text")
404 || contentType.endsWith("xml") )
405 writer = resp.getWriter();
411 || (ranges == null || ranges.isEmpty())
412 && req.getHeader("Range") == null
414 // Set the appropriate output headers
415 if (contentType != null) {
416 if (logger.isDebugEnabled())
417 logger.debug("contentType='" + contentType + "'");
418 resp.setContentType(contentType);
420 if (file != null && contentLength >= 0) {
421 if (logger.isDebugEnabled())
422 logger.debug("contentLength=" + contentLength);
423 if (contentLength < Integer.MAX_VALUE)
424 resp.setContentLength((int) contentLength);
426 // Set the content-length as String to be able to use a long
427 resp.setHeader("content-length", "" + contentLength);
430 InputStream renderResult = null;
433 // Serve the directory browser
435 renderResult = renderJson(user, folder);
436 } catch (InsufficientPermissionsException e) {
437 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
440 // Copy the input stream to our output stream (if requested)
443 resp.setBufferSize(output);
444 } catch (IllegalStateException e) {
449 if (needsContentDisposition(req))
450 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
452 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+URLEncoder.encode(file.getName(),"UTF-8"));
454 copy(file, renderResult, ostream, req, oldBody);
456 copy(file, renderResult, writer, req, oldBody);
457 if (file!=null) updateAccounting(owner, new Date(), contentLength);
458 } catch (ObjectNotFoundException e) {
459 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
461 } catch (InsufficientPermissionsException e) {
462 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
464 } catch (RpcException e) {
465 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
470 if (ranges == null || ranges.isEmpty())
472 // Partial content response.
473 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
475 if (ranges.size() == 1) {
476 Range range = (Range) ranges.get(0);
477 resp.addHeader("Content-Range", "bytes "
479 + "-" + range.end + "/"
481 long length = range.end - range.start + 1;
482 if (length < Integer.MAX_VALUE)
483 resp.setContentLength((int) length);
485 // Set the content-length as String to be able to use a long
486 resp.setHeader("content-length", "" + length);
488 if (contentType != null) {
489 if (logger.isDebugEnabled())
490 logger.debug("contentType='" + contentType + "'");
491 resp.setContentType(contentType);
496 resp.setBufferSize(output);
497 } catch (IllegalStateException e) {
502 copy(file, ostream, range, req, oldBody);
504 copy(file, writer, range, req, oldBody);
505 updateAccounting(owner, new Date(), contentLength);
506 } catch (ObjectNotFoundException e) {
507 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
509 } catch (InsufficientPermissionsException e) {
510 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
512 } catch (RpcException e) {
513 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
518 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
521 resp.setBufferSize(output);
522 } catch (IllegalStateException e) {
527 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
529 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
530 updateAccounting(owner, new Date(), contentLength);
531 } catch (ObjectNotFoundException e) {
532 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
534 } catch (InsufficientPermissionsException e) {
535 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
537 } catch (RpcException e) {
538 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
547 * Determines whether the user agent needs the Content-Disposition
548 * header to be set, in order to properly download a file.
550 * @param req the HTTP request
551 * @return true if the Content-Disposition HTTP header must be set
553 private boolean needsContentDisposition(HttpServletRequest req) {
554 /*String agent = req.getHeader("user-agent");
555 if (agent != null && agent.contains("MSIE"))
557 String dl = req.getParameter("dl");
564 * Sends a progress update on the amount of bytes received until now for
565 * a file that the current user is currently uploading.
567 * @param req the HTTP request
568 * @param resp the HTTP response
569 * @param parameter the value for the progress request parameter
570 * @param user the current user
571 * @param file the file being uploaded, or null if the request is about a new file
572 * @throws IOException if an I/O error occurs
574 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
575 String parameter, User user, FileHeaderDTO file) throws IOException {
576 String filename = file == null ? parameter : file.getName();
578 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
579 if (status == null) {
580 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
583 JSONObject json = new JSONObject();
584 json.put("bytesUploaded", status.getBytesUploaded()).
585 put("bytesTotal", status.getFileSize());
586 sendJson(req, resp, json.toString());
588 // Workaround for IE's broken caching behavior.
589 resp.setHeader("Expires", "-1");
591 } catch (RpcException e) {
592 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
594 } catch (JSONException e) {
595 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
601 * Server a POST request to create/modify a file or folder.
603 * @param req the HTTP request
604 * @param resp the HTTP response
605 * @exception IOException if an input/output error occurs
607 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
608 boolean authDeferred = getAuthDeferred(req);
609 if (!authDeferred && req.getParameterMap().size() > 1) {
610 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
613 String path = getInnerPath(req, PATH_FILES);
614 path = path.endsWith("/")? path: path + '/';
616 path = URLDecoder.decode(path, "UTF-8");
617 } catch (IllegalArgumentException e) {
618 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
621 // We only defer authenticating multipart POST requests.
623 if (!ServletFileUpload.isMultipartContent(req)) {
624 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
627 handleMultipart(req, resp, path);
631 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
632 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
633 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
634 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
635 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
636 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
637 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
640 createFolder(req, resp, path, newName);
641 else if (hasUpdateParam)
642 updateResource(req, resp, path);
643 else if (hasTrashParam)
644 trashResource(req, resp, path);
645 else if (hasRestoreParam)
646 restoreResource(req, resp, path);
647 else if (copyTo != null)
648 copyResource(req, resp, path, copyTo);
649 else if (moveTo != null)
650 moveResource(req, resp, path, moveTo);
651 else if (restoreVersion != null)
652 restoreVersion(req, resp, path, restoreVersion);
654 // IE with Gears uses POST for multiple uploads.
655 putResource(req, resp);
659 * Restores a previous version for a file.
661 * @param req the HTTP request
662 * @param resp the HTTP response
663 * @param path the resource path
664 * @param version the version number to restore
665 * @throws IOException if an I/O error occurs
667 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
668 final User user = getUser(req);
669 User owner = getOwner(req);
670 Object resource = null;
672 resource = getService().getResourceAtPath(owner.getId(), path, true);
673 } catch (ObjectNotFoundException e) {
674 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
676 } catch (RpcException e) {
677 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
680 if (resource instanceof FolderDTO) {
681 resp.sendError(HttpServletResponse.SC_CONFLICT);
686 final FileHeaderDTO file = (FileHeaderDTO) resource;
687 final int oldVersion = Integer.parseInt(version);
689 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
691 public Void call() throws Exception {
692 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
696 } catch (InsufficientPermissionsException e) {
697 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
698 } catch (ObjectNotFoundException e) {
699 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
700 } catch (RpcException e) {
701 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
702 } catch (GSSIOException e) {
703 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
704 } catch (QuotaExceededException e) {
705 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
706 } catch (NumberFormatException e) {
707 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
708 } catch (Exception e) {
709 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
714 * A method for handling multipart POST requests for uploading
715 * files from browser-based JavaScript clients.
717 * @param request the HTTP request
718 * @param response the HTTP response
719 * @param path the resource path
720 * @throws IOException in case an error occurs writing to the
723 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
724 if (logger.isDebugEnabled())
725 logger.debug("Multipart POST for resource: " + path);
727 User owner = getOwner(request);
728 boolean exists = true;
729 Object resource = null;
730 FileHeaderDTO file = null;
732 resource = getService().getResourceAtPath(owner.getId(), path, false);
733 } catch (ObjectNotFoundException e) {
735 } catch (RpcException e) {
736 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
741 if (resource instanceof FileHeaderDTO) {
742 file = (FileHeaderDTO) resource;
743 if (file.isDeleted()) {
744 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
748 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
753 String parentPath = null;
755 parentPath = getParentPath(path);
756 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
757 } catch (ObjectNotFoundException e) {
758 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
760 } catch (RpcException e) {
761 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
764 if (!(parent instanceof FolderDTO)) {
765 response.sendError(HttpServletResponse.SC_CONFLICT);
768 final FolderDTO folder = (FolderDTO) parent;
769 final String fileName = getLastElement(path);
771 FileItemIterator iter;
772 File uploadedFile = null;
774 // Create a new file upload handler.
775 ServletFileUpload upload = new ServletFileUpload();
776 StatusProgressListener progressListener = new StatusProgressListener(getService());
777 upload.setProgressListener(progressListener);
778 iter = upload.getItemIterator(request);
779 String dateParam = null;
781 while (iter.hasNext()) {
782 FileItemStream item = iter.next();
783 String name = item.getFieldName();
784 InputStream stream = item.openStream();
785 if (item.isFormField()) {
786 final String value = Streams.asString(stream);
787 if (name.equals(DATE_PARAMETER))
789 else if (name.equals(AUTHORIZATION_PARAMETER))
792 if (logger.isDebugEnabled())
793 logger.debug(name + ":" + value);
795 // Fetch the timestamp used to guard against replay attacks.
796 if (dateParam == null) {
797 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
803 timestamp = DateUtil.parseDate(dateParam).getTime();
804 } catch (DateParseException e) {
805 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
808 if (!isTimeValid(timestamp)) {
809 response.sendError(HttpServletResponse.SC_FORBIDDEN);
813 // Fetch the Authorization parameter and find the user specified in it.
815 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
818 String[] authParts = auth.split(" ");
819 if (authParts.length != 2) {
820 response.sendError(HttpServletResponse.SC_FORBIDDEN);
823 String username = authParts[0];
824 String signature = authParts[1];
827 user = getService().findUser(username);
828 } catch (RpcException e) {
829 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
833 response.sendError(HttpServletResponse.SC_FORBIDDEN);
836 request.setAttribute(USER_ATTRIBUTE, user);
838 // Remove the servlet path from the request URI.
839 String p = request.getRequestURI();
840 String servletPath = request.getContextPath() + request.getServletPath();
841 p = p.substring(servletPath.length());
842 // Validate the signature in the Authorization parameter.
843 String data = request.getMethod() + dateParam + p;
844 if (!isSignatureValid(signature, user, data)) {
845 response.sendError(HttpServletResponse.SC_FORBIDDEN);
849 progressListener.setUserId(user.getId());
850 progressListener.setFilename(fileName);
851 final String contentType = item.getContentType();
854 uploadedFile = getService().uploadFile(stream, user.getId());
855 } catch (IOException ex) {
856 throw new GSSIOException(ex, false);
858 FileHeaderDTO fileDTO = null;
859 final File upf = uploadedFile;
860 final FileHeaderDTO f = file;
863 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
865 public FileHeaderDTO call() throws Exception {
866 return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
870 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
872 public FileHeaderDTO call() throws Exception {
873 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
876 updateAccounting(owner, new Date(), fileDTO.getFileSize());
877 getService().removeFileUploadProgress(user.getId(), fileName);
880 // We can't return 204 here since GWT's onSubmitComplete won't fire.
881 response.setContentType("text/html");
882 response.getWriter().print("<pre></pre>");
883 } catch (FileUploadException e) {
884 String error = "Error while uploading file";
885 logger.error(error, e);
886 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
887 } catch (GSSIOException e) {
888 if (uploadedFile != null && uploadedFile.exists())
889 uploadedFile.delete();
890 String error = "Error while uploading file";
892 logger.error(error, e);
894 logger.debug(error, e);
895 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
896 } catch (DuplicateNameException e) {
897 if (uploadedFile != null && uploadedFile.exists())
898 uploadedFile.delete();
899 String error = "The specified file name already exists in this folder";
900 logger.error(error, e);
901 response.sendError(HttpServletResponse.SC_CONFLICT, error);
903 } catch (InsufficientPermissionsException e) {
904 if (uploadedFile != null && uploadedFile.exists())
905 uploadedFile.delete();
906 String error = "You don't have the necessary permissions";
907 logger.error(error, e);
908 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
910 } catch (QuotaExceededException e) {
911 if (uploadedFile != null && uploadedFile.exists())
912 uploadedFile.delete();
913 String error = "Not enough free space available";
914 if (logger.isDebugEnabled())
915 logger.debug(error, e);
916 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
918 } catch (ObjectNotFoundException e) {
919 if (uploadedFile != null && uploadedFile.exists())
920 uploadedFile.delete();
921 String error = "A specified object was not found";
922 logger.error(error, e);
923 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
924 } catch (RpcException e) {
925 if (uploadedFile != null && uploadedFile.exists())
926 uploadedFile.delete();
927 String error = "An error occurred while communicating with the service";
928 logger.error(error, e);
929 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
930 } catch (Exception e) {
931 if (uploadedFile != null && uploadedFile.exists())
932 uploadedFile.delete();
933 String error = "An internal server error occurred";
934 logger.error(error, e);
935 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
939 private String getBackupFilename(FileHeaderDTO file, String filename){
940 List<FileHeaderDTO> deletedFiles = new ArrayList<FileHeaderDTO>();
942 deletedFiles = getService().getDeletedFiles(file.getOwner().getId());
944 catch(ObjectNotFoundException e){
946 } catch (RpcException e) {
949 List<FileHeaderDTO> filesInSameFolder = new ArrayList<FileHeaderDTO>();
950 for(FileHeaderDTO deleted : deletedFiles)
951 if(deleted.getFolder().getId().equals(file.getFolder().getId()))
952 filesInSameFolder.add(deleted);
954 String filenameToCheck = filename;
955 for(FileHeaderDTO same : filesInSameFolder)
956 if(same.getName().startsWith(filename)){
957 String toCheck=same.getName().substring(filename.length(),same.getName().length());
958 if(toCheck.startsWith(" ")){
961 test = Integer.valueOf(toCheck.replace(" ",""));
963 catch(NumberFormatException e){
964 //do nothing since string is not a number
971 return filename+" "+i;
975 * Move the resource in the specified path to the specified destination.
977 * @param req the HTTP request
978 * @param resp the HTTP response
979 * @param path the path of the resource
980 * @param moveTo the destination of the move procedure
981 * @throws IOException if an input/output error occurs
983 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
984 final User user = getUser(req);
985 User owner = getOwner(req);
986 Object resource = null;
988 resource = getService().getResourceAtPath(owner.getId(), path, true);
989 } catch (ObjectNotFoundException e) {
990 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
992 } catch (RpcException e) {
993 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
997 String destination = null;
998 User destOwner = null;
999 boolean exists = true;
1001 destination = getDestinationPath(req, encodePath(moveTo));
1002 destination = URLDecoder.decode(destination, "UTF-8");
1003 destOwner = getDestinationOwner(req);
1004 getService().getResourceAtPath(destOwner.getId(), destination, true);
1005 } catch (ObjectNotFoundException e) {
1007 } catch (URISyntaxException e) {
1008 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1010 } catch (RpcException e) {
1011 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1015 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1020 final User dOwner = destOwner;
1021 final String dest = destination;
1022 if (resource instanceof FolderDTO) {
1023 final FolderDTO folder = (FolderDTO) resource;
1024 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1026 public Void call() throws Exception {
1027 getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1032 final FileHeaderDTO file = (FileHeaderDTO) resource;
1033 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1035 public Void call() throws Exception {
1036 getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1042 } catch (InsufficientPermissionsException e) {
1043 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1044 } catch (ObjectNotFoundException e) {
1045 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1046 } catch (RpcException e) {
1047 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1048 } catch (DuplicateNameException e) {
1049 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1050 } catch (GSSIOException e) {
1051 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1052 } catch (QuotaExceededException e) {
1053 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1054 } catch (Exception e) {
1055 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1060 * Copy the resource in the specified path to the specified destination.
1062 * @param req the HTTP request
1063 * @param resp the HTTP response
1064 * @param path the path of the resource
1065 * @param copyTo the destination of the copy procedure
1066 * @throws IOException if an input/output error occurs
1068 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1069 final User user = getUser(req);
1070 User owner = getOwner(req);
1071 Object resource = null;
1073 resource = getService().getResourceAtPath(owner.getId(), path, true);
1074 } catch (ObjectNotFoundException e) {
1075 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1077 } catch (RpcException e) {
1078 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1082 String destination = null;
1083 User destOwner = null;
1084 boolean exists = true;
1086 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1087 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1088 destOwner = getDestinationOwner(req);
1089 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1090 } catch (ObjectNotFoundException e) {
1092 } catch (URISyntaxException e) {
1093 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1095 } catch (RpcException e) {
1096 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1100 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1105 final User dOwner = destOwner;
1106 final String dest = destination;
1107 if (resource instanceof FolderDTO) {
1108 final FolderDTO folder = (FolderDTO) resource;
1109 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1111 public Void call() throws Exception {
1112 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1117 final FileHeaderDTO file = (FileHeaderDTO) resource;
1118 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1120 public Void call() throws Exception {
1121 getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1126 } catch (InsufficientPermissionsException e) {
1127 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1128 } catch (ObjectNotFoundException e) {
1129 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1130 } catch (RpcException e) {
1131 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1132 } catch (DuplicateNameException e) {
1133 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1134 } catch (GSSIOException e) {
1135 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1136 } catch (QuotaExceededException e) {
1137 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1138 } catch (Exception e) {
1139 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1143 private String encodePath(String path) throws UnsupportedEncodingException{
1144 StringTokenizer str = new StringTokenizer(path, "/:", true);
1145 String result = new String();
1146 while(str.hasMoreTokens()){
1147 String token = str.nextToken();
1148 if(!token.equals("/") && !token.equals(":"))
1149 token = URLEncoder.encode(token,"UTF-8");
1150 result = result + token;
1155 * A helper method that extracts the relative resource path,
1156 * after removing the 'files' namespace.
1157 * The path returned is <i>not</i> URL-decoded.
1159 * @param req the HTTP request
1160 * @param path the specified path
1161 * @return the path relative to the root folder
1162 * @throws URISyntaxException
1163 * @throws RpcException in case an error occurs while communicating
1165 * @throws UnsupportedEncodingException
1167 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1168 URI uri = new URI(path);
1169 String dest = uri.getRawPath();
1170 // Remove the context path from the destination URI.
1171 String contextPath = req.getContextPath();
1172 if (!dest.startsWith(contextPath))
1173 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1174 dest = dest.substring(contextPath.length());
1175 // Remove the servlet path from the destination URI.
1176 String servletPath = req.getServletPath();
1177 if (!dest.startsWith(servletPath))
1178 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1179 dest = dest.substring(servletPath.length());
1180 // Strip the username part
1181 if (dest.length() < 2)
1182 throw new URISyntaxException(dest, "No username in the destination URI");
1183 int slash = dest.substring(1).indexOf('/');
1185 throw new URISyntaxException(dest, "No username in the destination URI");
1186 // Decode the user to get the proper characters (mainly the @)
1187 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1189 o = getService().findUser(owner);
1191 throw new URISyntaxException(dest, "User " + owner + " not found");
1193 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1194 dest = dest.substring(slash + 1);
1196 // Chop the resource namespace part
1197 dest = dest.substring(RequestHandler.PATH_FILES.length());
1199 dest = dest.endsWith("/")? dest: dest + '/';
1204 * Move the resource in the specified path to the trash bin.
1206 * @param req the HTTP request
1207 * @param resp the HTTP response
1208 * @param path the path of the resource
1209 * @throws IOException if an input/output error occurs
1211 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1212 final User user = getUser(req);
1213 User owner = getOwner(req);
1214 Object resource = null;
1216 resource = getService().getResourceAtPath(owner.getId(), path, true);
1217 } catch (ObjectNotFoundException e) {
1218 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1220 } catch (RpcException e) {
1221 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1226 if (resource instanceof FolderDTO) {
1227 final FolderDTO folder = (FolderDTO) resource;
1228 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1230 public Void call() throws Exception {
1231 getService().moveFolderToTrash(user.getId(), folder.getId());
1236 final FileHeaderDTO file = (FileHeaderDTO) resource;
1237 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1239 public Void call() throws Exception {
1240 getService().moveFileToTrash(user.getId(), file.getId());
1245 } catch (InsufficientPermissionsException e) {
1246 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1247 } catch (ObjectNotFoundException e) {
1248 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1249 } catch (RpcException e) {
1250 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1251 } catch (Exception e) {
1252 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1257 * Restore the resource in the specified path from the trash bin.
1259 * @param req the HTTP request
1260 * @param resp the HTTP response
1261 * @param path the path of the resource
1262 * @throws IOException if an input/output error occurs
1264 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1265 final User user = getUser(req);
1266 User owner = getOwner(req);
1267 Object resource = null;
1269 resource = getService().getResourceAtPath(owner.getId(), path, false);
1270 } catch (ObjectNotFoundException e) {
1271 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1273 } catch (RpcException e) {
1274 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1279 if (resource instanceof FolderDTO) {
1280 final FolderDTO folder = (FolderDTO) resource;
1281 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1283 public Void call() throws Exception {
1284 getService().removeFolderFromTrash(user.getId(), folder.getId());
1289 final FileHeaderDTO file = (FileHeaderDTO) resource;
1290 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1292 public Void call() throws Exception {
1293 getService().removeFileFromTrash(user.getId(), file.getId());
1298 } catch (InsufficientPermissionsException e) {
1299 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1300 } catch (ObjectNotFoundException e) {
1301 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1302 } catch (RpcException e) {
1303 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1304 } catch (Exception e) {
1305 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1310 * Update the resource in the specified path.
1312 * @param req the HTTP request
1313 * @param resp the HTTP response
1314 * @param path the path of the resource
1315 * @throws IOException if an input/output error occurs
1317 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1318 final User user = getUser(req);
1319 User owner = getOwner(req);
1320 Object resource = null;
1322 resource = getService().getResourceAtPath(owner.getId(), path, false);
1323 } catch (ObjectNotFoundException e) {
1324 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1326 } catch (RpcException e) {
1327 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1330 //use utf-8 encoding for reading request
1331 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1332 StringBuffer input = new StringBuffer();
1334 JSONObject json = null;
1335 while ((line = reader.readLine()) != null)
1339 json = new JSONObject(input.toString());
1340 if (logger.isDebugEnabled())
1341 logger.debug("JSON update: " + json);
1342 if (resource instanceof FolderDTO) {
1343 final FolderDTO folder = (FolderDTO) resource;
1344 String name = json.optString("name");
1345 if (!name.isEmpty()){
1347 name = URLDecoder.decode(name, "UTF-8");
1348 } catch (IllegalArgumentException e) {
1349 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1352 final String fName = name;
1353 FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1355 public FolderDTO call() throws Exception {
1356 return getService().modifyFolder(user.getId(), folder.getId(), fName);
1360 String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1361 String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1362 parentUrl = parentUrl.replaceAll(fpath, "");
1363 if(!parentUrl.endsWith("/"))
1364 parentUrl = parentUrl+"/";
1365 parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
1366 resp.getWriter().println(parentUrl);
1369 JSONArray permissions = json.optJSONArray("permissions");
1370 if (permissions != null) {
1371 final Set<PermissionDTO> perms = parsePermissions(user, permissions);
1372 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1374 public Object call() throws Exception {
1375 getService().setFolderPermissions(user.getId(), folder.getId(), perms);
1382 final FileHeaderDTO file = (FileHeaderDTO) resource;
1384 if (json.opt("name") != null)
1385 name = json.optString("name");
1386 Long modificationDate = null;
1387 if (json.optLong("modificationDate") != 0)
1388 modificationDate = json.optLong("modificationDate");
1389 JSONArray tagset = json.optJSONArray("tags");
1391 StringBuffer t = new StringBuffer();
1392 if (tagset != null) {
1393 for (int i = 0; i < tagset.length(); i++)
1394 t.append(tagset.getString(i) + ',');
1395 tags = t.toString();
1397 if (name != null || tags != null || modificationDate != null) {
1398 final String fName = name;
1399 final String fTags = tags;
1400 final Date mDate = modificationDate != null? new Date(modificationDate): null;
1401 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1403 public Object call() throws Exception {
1404 getService().updateFile(user.getId(), file.getId(), fName, fTags, mDate);
1411 JSONArray permissions = json.optJSONArray("permissions");
1412 Set<PermissionDTO> perms = null;
1413 if (permissions != null)
1414 perms = parsePermissions(user, permissions);
1415 Boolean readForAll = null;
1416 if (json.opt("readForAll") != null)
1417 readForAll = json.optBoolean("readForAll");
1418 if (perms != null || readForAll != null) {
1419 final Boolean fReadForAll = readForAll;
1420 final Set<PermissionDTO> fPerms = perms;
1421 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1423 public Object call() throws Exception {
1424 getService().setFilePermissions(user.getId(), file.getId(), fReadForAll, fPerms);
1431 if (json.opt("versioned") != null) {
1432 final boolean versioned = json.getBoolean("versioned");
1433 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1435 public Object call() throws Exception {
1436 getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
1443 } catch (JSONException e) {
1444 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1445 } catch (InsufficientPermissionsException e) {
1446 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1447 } catch (ObjectNotFoundException e) {
1448 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1449 } catch (DuplicateNameException e) {
1450 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1451 } catch (RpcException e) {
1452 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1453 } catch (Exception e) {
1454 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1460 * Helper method to convert a JSON array of permissions into a set of
1461 * PermissionDTO objects.
1463 * @param user the current user
1464 * @param permissions the JSON array to parse
1465 * @return the parsed set of permissions
1466 * @throws JSONException if there was an error parsing the JSON object
1467 * @throws RpcException if there was an error communicating with the EJB
1468 * @throws ObjectNotFoundException if the user could not be found
1469 * @throws UnsupportedEncodingException
1471 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1472 throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1473 if (permissions == null)
1475 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1476 for (int i = 0; i < permissions.length(); i++) {
1477 JSONObject j = permissions.getJSONObject(i);
1478 PermissionDTO perm = new PermissionDTO();
1479 perm.setModifyACL(j.optBoolean("modifyACL"));
1480 perm.setRead(j.optBoolean("read"));
1481 perm.setWrite(j.optBoolean("write"));
1482 String permUser = j.optString("user");
1483 if (!permUser.isEmpty()) {
1484 User u = getService().findUser(permUser);
1486 throw new ObjectNotFoundException("User " + permUser + " not found");
1487 perm.setUser(u.getDTO());
1489 // 31/8/2009: Add optional groupUri which takes priority if it exists
1490 String permGroupUri = j.optString("groupUri");
1491 String permGroup = j.optString("group");
1492 if (!permGroupUri.isEmpty()) {
1493 String[] names = permGroupUri.split("/");
1494 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1495 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1496 User u = getService().findUser(usr);
1498 throw new ObjectNotFoundException("User " + permUser + " not found");
1499 GroupDTO g = getService().getGroup(u.getId(), grp);
1502 else if (!permGroup.isEmpty()) {
1503 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1506 if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1507 throw new JSONException("A permission must correspond to either a user or a group");
1514 * Creates a new folder with the specified name under the folder in the provided path.
1516 * @param req the HTTP request
1517 * @param resp the HTTP response
1518 * @param path the parent folder path
1519 * @param folderName the name of the new folder
1520 * @throws IOException if an input/output error occurs
1522 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1523 if (logger.isDebugEnabled())
1524 logger.debug("Creating folder " + folderName + " in '" + path);
1526 final User user = getUser(req);
1527 User owner = getOwner(req);
1528 boolean exists = true;
1530 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1531 } catch (ObjectNotFoundException e) {
1533 } catch (RpcException e) {
1534 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1539 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1540 ", " + METHOD_HEAD);
1541 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1547 parent = getService().getResourceAtPath(owner.getId(), path, true);
1548 } catch (ObjectNotFoundException e) {
1549 resp.sendError(HttpServletResponse.SC_CONFLICT);
1551 } catch (RpcException e) {
1552 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1556 if (parent instanceof FolderDTO) {
1557 final FolderDTO folder = (FolderDTO) parent;
1558 FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1560 public FolderDTO call() throws Exception {
1561 return getService().createFolder(user.getId(), folder.getId(), folderName);
1565 String newResource = getApiRoot() + newFolder.getURI();
1566 resp.setHeader("Location", newResource);
1567 resp.setContentType("text/plain");
1568 PrintWriter out = resp.getWriter();
1569 out.println(newResource);
1571 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1574 } catch (DuplicateNameException e) {
1575 resp.sendError(HttpServletResponse.SC_CONFLICT);
1577 } catch (InsufficientPermissionsException e) {
1578 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1580 } catch (ObjectNotFoundException e) {
1581 resp.sendError(HttpServletResponse.SC_CONFLICT);
1583 } catch (RpcException e) {
1584 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1586 } catch (Exception e) {
1587 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1590 resp.setStatus(HttpServletResponse.SC_CREATED);
1596 * @throws IOException
1597 * @throws FileNotFoundException
1599 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1600 String path = getInnerPath(req, PATH_FILES);
1602 path = URLDecoder.decode(path, "UTF-8");
1603 } catch (IllegalArgumentException e) {
1604 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1607 if (logger.isDebugEnabled())
1608 logger.debug("Updating resource: " + path);
1610 final User user = getUser(req);
1611 User owner = getOwner(req);
1612 boolean exists = true;
1613 Object resource = null;
1614 FileHeaderDTO file = null;
1616 resource = getService().getResourceAtPath(owner.getId(), path, false);
1617 } catch (ObjectNotFoundException e) {
1619 } catch (RpcException e) {
1620 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1625 if (resource instanceof FileHeaderDTO)
1626 file = (FileHeaderDTO) resource;
1628 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1631 boolean result = true;
1633 // Temporary content file used to support partial PUT.
1634 File contentFile = null;
1636 Range range = parseContentRange(req, resp);
1638 InputStream resourceInputStream = null;
1640 // Append data specified in ranges to existing content for this
1641 // resource - create a temporary file on the local filesystem to
1642 // perform this operation.
1643 // Assume just one range is specified for now
1644 if (range != null) {
1646 contentFile = executePartialPut(req, range, path);
1647 } catch (RpcException e) {
1648 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1650 } catch (ObjectNotFoundException e) {
1651 resp.sendError(HttpServletResponse.SC_CONFLICT);
1653 } catch (InsufficientPermissionsException e) {
1654 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1657 resourceInputStream = new FileInputStream(contentFile);
1659 resourceInputStream = req.getInputStream();
1662 FolderDTO folder = null;
1663 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1664 if (!(parent instanceof FolderDTO)) {
1665 resp.sendError(HttpServletResponse.SC_CONFLICT);
1668 folder = (FolderDTO) parent;
1669 final String name = getLastElement(path);
1670 final String mimeType = context.getMimeType(name);
1671 File uploadedFile = null;
1673 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1674 } catch (IOException ex) {
1675 throw new GSSIOException(ex, false);
1677 FileHeaderDTO fileDTO = null;
1678 final File uploadedf = uploadedFile;
1679 final FolderDTO parentf = folder;
1680 final FileHeaderDTO f = file;
1682 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1684 public FileHeaderDTO call() throws Exception {
1685 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1689 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1691 public FileHeaderDTO call() throws Exception {
1692 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1696 updateAccounting(owner, new Date(), fileDTO.getFileSize());
1697 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1698 } catch(ObjectNotFoundException e) {
1700 } catch (RpcException e) {
1701 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1703 } catch (IOException e) {
1704 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1706 } catch (GSSIOException e) {
1707 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1709 } catch (DuplicateNameException e) {
1710 resp.sendError(HttpServletResponse.SC_CONFLICT);
1712 } catch (InsufficientPermissionsException e) {
1713 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1715 } catch (QuotaExceededException e) {
1716 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1718 } catch (Exception e) {
1719 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1725 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1727 resp.setStatus(HttpServletResponse.SC_CREATED);
1729 resp.sendError(HttpServletResponse.SC_CONFLICT);
1733 * Delete a resource.
1735 * @param req The servlet request we are processing
1736 * @param resp The servlet response we are processing
1737 * @throws IOException if the response cannot be sent
1739 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1740 String path = getInnerPath(req, PATH_FILES);
1741 if (logger.isDebugEnabled())
1742 logger.debug("Deleting resource '" + path);
1743 path = URLDecoder.decode(path, "UTF-8");
1744 final User user = getUser(req);
1745 User owner = getOwner(req);
1746 boolean exists = true;
1747 Object object = null;
1749 object = getService().getResourceAtPath(owner.getId(), path, false);
1750 } catch (ObjectNotFoundException e) {
1752 } catch (RpcException e) {
1753 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1758 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1762 FolderDTO folder = null;
1763 FileHeaderDTO file = null;
1764 if (object instanceof FolderDTO)
1765 folder = (FolderDTO) object;
1767 file = (FileHeaderDTO) object;
1771 final FileHeaderDTO f = file;
1772 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1774 public Void call() throws Exception {
1775 getService().deleteFile(user.getId(), f.getId());
1779 } catch (InsufficientPermissionsException e) {
1780 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1782 } catch (ObjectNotFoundException e) {
1783 // Although we had already found the object, it was
1784 // probably deleted from another thread.
1785 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1787 } catch (RpcException e) {
1788 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1790 } catch (Exception e) {
1791 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1794 else if (folder != null)
1796 final FolderDTO fo = folder;
1797 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1799 public Void call() throws Exception {
1800 getService().deleteFolder(user.getId(), fo.getId());
1804 } catch (InsufficientPermissionsException e) {
1805 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1807 } catch (ObjectNotFoundException e) {
1808 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1810 } catch (RpcException e) {
1811 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1813 } catch (Exception e) {
1814 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1817 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1822 * Return an InputStream to a JSON representation of the contents
1823 * of this directory.
1825 * @param user the user that made the request
1826 * @param folder the specified directory
1827 * @return an input stream with the rendered contents
1828 * @throws IOException if the response cannot be sent
1829 * @throws ServletException
1830 * @throws InsufficientPermissionsException if the user does not have
1831 * the necessary privileges to read the directory
1833 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1834 ServletException, InsufficientPermissionsException {
1835 JSONObject json = new JSONObject();
1837 json.put("name", folder.getName()).
1838 put("owner", folder.getOwner().getUsername()).
1839 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1840 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1841 put("deleted", folder.isDeleted());
1842 if (folder.getAuditInfo().getModifiedBy() != null)
1843 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1844 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1845 if (folder.getParent() != null) {
1846 JSONObject j = new JSONObject();
1847 j.put("uri", getApiRoot() + folder.getParent().getURI());
1848 j.put("name", folder.getParent().getName());
1849 json.put("parent", j);
1851 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1852 for (FolderDTO f: folder.getSubfolders())
1853 if (!f.isDeleted()) {
1854 JSONObject j = new JSONObject();
1855 j.put("name", f.getName()).
1856 put("uri", getApiRoot() + f.getURI());
1859 json.put("folders", subfolders);
1860 List<JSONObject> files = new ArrayList<JSONObject>();
1861 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1862 for (FileHeaderDTO f: fileHeaders) {
1863 JSONObject j = new JSONObject();
1864 j.put("name", f.getName()).
1865 put("owner", f.getOwner().getUsername()).
1866 put("deleted", f.isDeleted()).
1867 put("version", f.getVersion()).
1868 put("content", f.getMimeType()).
1869 put("size", f.getFileSize()).
1870 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1871 put("path", f.getFolder().getPath()).
1872 put("uri", getApiRoot() + f.getURI());
1873 if (f.getAuditInfo().getModificationDate() != null)
1874 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1877 json.put("files", files);
1878 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1879 json.put("permissions", renderJson(perms));
1880 } catch (JSONException e) {
1881 throw new ServletException(e);
1882 } catch (ObjectNotFoundException e) {
1883 throw new ServletException(e);
1884 } catch (RpcException e) {
1885 throw new ServletException(e);
1888 // Prepare a writer to a buffered area
1889 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1890 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1891 PrintWriter writer = new PrintWriter(osWriter);
1893 // Return an input stream to the underlying bytes
1894 writer.write(json.toString());
1896 return new ByteArrayInputStream(stream.toByteArray());
1900 * Return a String with a JSON representation of the metadata
1901 * of the specified folder.
1902 * @throws RpcException
1903 * @throws InsufficientPermissionsException
1904 * @throws ObjectNotFoundException
1906 private String renderJsonMetadata(User user, FolderDTO folder)
1907 throws ServletException, InsufficientPermissionsException {
1908 // Check if the user has read permission.
1910 if (!getService().canReadFolder(user.getId(), folder.getId()))
1911 throw new InsufficientPermissionsException();
1912 } catch (ObjectNotFoundException e) {
1913 throw new ServletException(e);
1914 } catch (RpcException e) {
1915 throw new ServletException(e);
1918 JSONObject json = new JSONObject();
1920 json.put("name", folder.getName()).
1921 put("owner", folder.getOwner().getUsername()).
1922 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1923 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1924 put("deleted", folder.isDeleted());
1925 if (folder.getAuditInfo().getModifiedBy() != null)
1926 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1927 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1928 } catch (JSONException e) {
1929 throw new ServletException(e);
1931 return json.toString();
1935 * Return a String with a JSON representation of the metadata
1936 * of the specified file. If an old file body is provided, then
1937 * the metadata of that particular version will be returned.
1939 * @param user the user that made the request
1940 * @param file the specified file header
1941 * @param oldBody the version number
1942 * @return the JSON-encoded file
1943 * @throws ServletException
1944 * @throws InsufficientPermissionsException if the user does not have
1945 * the necessary privileges to read the directory
1947 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1948 throws ServletException, InsufficientPermissionsException {
1949 JSONObject json = new JSONObject();
1951 // Need to encode file name in order to properly display it in the web client.
1952 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1953 put("owner", file.getOwner().getUsername()).
1954 put("versioned", file.isVersioned()).
1955 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1956 put("readForAll", file.isReadForAll()).
1957 put("tags", renderJson(file.getTags())).
1958 put("path", file.getFolder().getPath()).
1959 put("uri", getApiRoot() + file.getURI()).
1960 put("deleted", file.isDeleted());
1961 JSONObject j = new JSONObject();
1962 j.put("uri", getApiRoot() + file.getFolder().getURI()).
1963 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1964 json.put("folder", j);
1965 if (oldBody != null)
1966 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1967 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1968 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1969 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
1970 put("content", oldBody.getMimeType()).
1971 put("size", oldBody.getFileSize());
1973 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1974 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1975 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1976 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
1977 put("content", file.getMimeType()).
1978 put("size", file.getFileSize());
1979 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1980 json.put("permissions", renderJson(perms));
1981 } catch (JSONException e) {
1982 throw new ServletException(e);
1983 } catch (ObjectNotFoundException e) {
1984 throw new ServletException(e);
1985 } catch (RpcException e) {
1986 throw new ServletException(e);
1987 } catch (UnsupportedEncodingException e) {
1988 throw new ServletException(e);
1991 return json.toString();
1995 * Return a String with a JSON representation of the
1996 * specified set of permissions.
1998 * @param permissions the set of permissions
1999 * @return the JSON-encoded object
2000 * @throws JSONException
2001 * @throws UnsupportedEncodingException
2003 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2004 JSONArray perms = new JSONArray();
2005 for (PermissionDTO p: permissions) {
2006 JSONObject permission = new JSONObject();
2007 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2008 if (p.getUser() != null)
2009 permission.put("user", p.getUser().getUsername());
2010 if (p.getGroup() != null) {
2011 GroupDTO group = p.getGroup();
2012 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2013 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2015 perms.put(permission);
2021 * Return a String with a JSON representation of the
2022 * specified collection of tags.
2024 * @param tags the collection of tags
2025 * @return the JSON-encoded object
2026 * @throws JSONException
2027 * @throws UnsupportedEncodingException
2029 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2030 JSONArray tagArray = new JSONArray();
2031 for (String t: tags)
2032 tagArray.put(URLEncoder.encode(t,"UTF-8"));
2037 * Retrieves the user who owns the destination namespace, for a
2038 * copy or move request.
2040 * @param req the HTTP request
2041 * @return the owner of the namespace
2043 protected User getDestinationOwner(HttpServletRequest req) {
2044 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2048 * A helper inner class for updating the progress status of a file upload.
2052 public static class StatusProgressListener implements ProgressListener {
2053 private int percentLogged = 0;
2054 private long bytesTransferred = 0;
2056 private long fileSize = -100;
2058 private long tenKBRead = -1;
2060 private Long userId;
2062 private String filename;
2064 private ExternalAPI service;
2066 public StatusProgressListener(ExternalAPI aService) {
2071 * Modify the userId.
2073 * @param aUserId the userId to set
2075 public void setUserId(Long aUserId) {
2080 * Modify the filename.
2082 * @param aFilename the filename to set
2084 public void setFilename(String aFilename) {
2085 filename = aFilename;
2088 public void update(long bytesRead, long contentLength, int items) {
2089 //monitoring per percent of bytes uploaded
2090 bytesTransferred = bytesRead;
2091 if (fileSize != contentLength)
2092 fileSize = contentLength;
2093 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2095 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2096 if (percent != percentLogged){
2097 percentLogged = percent;
2099 if (userId != null && filename != null)
2100 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2101 } catch (ObjectNotFoundException e) {
2102 // Swallow the exception since it is going to be caught
2103 // by previously called methods