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.webdav.Range;
37 import java.io.BufferedReader;
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
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;
50 import java.net.URISyntaxException;
51 import java.net.URLDecoder;
52 import java.net.URLEncoder;
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.Date;
56 import java.util.HashSet;
57 import java.util.List;
59 import java.util.StringTokenizer;
61 import javax.servlet.ServletContext;
62 import javax.servlet.ServletException;
63 import javax.servlet.ServletOutputStream;
64 import javax.servlet.http.HttpServletRequest;
65 import javax.servlet.http.HttpServletResponse;
67 import org.apache.commons.fileupload.FileItemIterator;
68 import org.apache.commons.fileupload.FileItemStream;
69 import org.apache.commons.fileupload.FileUploadException;
70 import org.apache.commons.fileupload.ProgressListener;
71 import org.apache.commons.fileupload.servlet.ServletFileUpload;
72 import org.apache.commons.fileupload.util.Streams;
73 import org.apache.commons.httpclient.util.DateParseException;
74 import org.apache.commons.httpclient.util.DateUtil;
75 import org.apache.commons.logging.Log;
76 import org.apache.commons.logging.LogFactory;
77 import org.json.JSONArray;
78 import org.json.JSONException;
79 import org.json.JSONObject;
83 * A class that handles operations on the 'files' namespace.
87 public class FilesHandler extends RequestHandler {
89 * The request parameter name for fetching a different version.
91 private static final String VERSION_PARAM = "version";
94 * The request attribute containing the owner of the destination URI
95 * in a copy or move request.
97 private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
99 private static final int TRACK_PROGRESS_PERCENT = 5;
102 * The form parameter name that contains the signature in a browser POST upload.
104 private static final String AUTHORIZATION_PARAMETER = "Authorization";
107 * The form parameter name that contains the date in a browser POST upload.
109 private static final String DATE_PARAMETER = "Date";
112 * The request parameter name for making an upload progress request.
114 private static final String PROGRESS_PARAMETER = "progress";
117 * The request parameter name for restoring a previous version of a file.
119 private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
124 private static Log logger = LogFactory.getLog(FilesHandler.class);
127 * The servlet context provided by the call site.
129 private ServletContext context;
132 * @param servletContext
134 public FilesHandler(ServletContext servletContext) {
135 context = servletContext;
139 * Serve the specified resource, optionally including the data content.
141 * @param req The servlet request we are processing
142 * @param resp The servlet response we are creating
143 * @param content Should the content be included?
145 * @exception IOException if an input/output error occurs
146 * @exception ServletException if a servlet-specified error occurs
147 * @throws RpcException
148 * @throws InsufficientPermissionsException
149 * @throws ObjectNotFoundException
152 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
153 throws IOException, ServletException {
154 boolean authDeferred = getAuthDeferred(req);
155 String path = getInnerPath(req, PATH_FILES);
159 path = URLDecoder.decode(path, "UTF-8");
160 } catch (IllegalArgumentException e) {
161 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
164 String progress = req.getParameter(PROGRESS_PARAMETER);
166 if (logger.isDebugEnabled())
168 logger.debug("Serving resource '" + path + "' headers and data");
170 logger.debug("Serving resource '" + path + "' headers only");
172 User user = getUser(req);
173 User owner = getOwner(req);
174 if (user == null) user = owner;
175 boolean exists = true;
176 Object resource = null;
177 FileHeaderDTO file = null;
178 FolderDTO folder = null;
180 resource = getService().getResourceAtPath(owner.getId(), path, false);
181 } catch (ObjectNotFoundException e) {
183 } catch (RpcException e) {
184 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
190 // We do not want to leak information if the request
191 // was not authenticated.
192 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
195 // A request for upload progress.
196 if (progress != null && content) {
197 serveProgress(req, resp, progress, user, null);
201 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
205 if (resource instanceof FolderDTO)
206 folder = (FolderDTO) resource;
208 file = (FileHeaderDTO) resource;
210 // Now it's time to perform the deferred authentication check.
211 // Since regular signature checking was already performed,
212 // we need to check the read-all flag or the signature-in-parameters.
214 if (file != null && !file.isReadForAll() && content) {
215 // Check for GET with the signature in the request parameters.
216 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
217 String dateParam = req.getParameter(DATE_PARAMETER);
218 if (auth == null || dateParam == null) {
219 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
225 timestamp = DateUtil.parseDate(dateParam).getTime();
226 } catch (DateParseException e) {
227 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
230 if (!isTimeValid(timestamp)) {
231 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
235 // Fetch the Authorization parameter and find the user specified in it.
236 String[] authParts = auth.split(" ");
237 if (authParts.length != 2) {
238 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
241 String username = authParts[0];
242 String signature = authParts[1];
245 user = getService().findUser(username);
246 } catch (RpcException e) {
247 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
251 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
254 req.setAttribute(USER_ATTRIBUTE, user);
256 // Remove the servlet path from the request URI.
257 String p = req.getRequestURI();
258 String servletPath = req.getContextPath() + req.getServletPath();
259 p = p.substring(servletPath.length());
260 // Validate the signature in the Authorization parameter.
261 String data = req.getMethod() + dateParam + p;
262 if (!isSignatureValid(signature, user, data)) {
263 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
266 } else if (file != null && !file.isReadForAll() || file == null) {
267 // Check for a read-for-all file request.
268 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
272 // If the resource is not a collection, and the resource path
273 // ends with "/" or "\", return NOT FOUND.
275 if (path.endsWith("/") || path.endsWith("\\")) {
276 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
280 // Workaround for IE's broken caching behavior.
282 resp.setHeader("Expires", "-1");
284 // A request for upload progress.
285 if (progress != null && content) {
287 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
290 serveProgress(req, resp, progress, user, file);
294 // Fetch the version to retrieve, if specified.
295 String verStr = req.getParameter(VERSION_PARAM);
297 FileBodyDTO oldBody = null;
298 if (verStr != null && file != null)
300 version = Integer.valueOf(verStr);
301 } catch (NumberFormatException e) {
302 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
307 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
308 } catch (RpcException e) {
309 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
311 } catch (ObjectNotFoundException e) {
312 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
314 } catch (InsufficientPermissionsException e) {
315 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
319 // Check if the conditions specified in the optional If headers are
320 // satisfied. Doing this for folders would require recursive checking
321 // for all of their children, which in turn would defy the purpose of
324 // Checking If headers.
325 if (!checkIfHeaders(req, resp, file, oldBody))
328 // Find content type.
329 String contentType = null;
331 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
332 if (contentType == null) {
333 contentType = context.getMimeType(file.getName());
334 file.setMimeType(contentType);
337 contentType = "application/json;charset=UTF-8";
339 ArrayList ranges = null;
340 long contentLength = -1L;
343 // Parse range specifier
344 ranges = parseRange(req, resp, file, oldBody);
346 resp.setHeader("ETag", getETag(file, oldBody));
347 // Last-Modified header
348 String lastModified = oldBody == null ?
349 getLastModifiedHttp(file.getAuditInfo()) :
350 getLastModifiedHttp(oldBody.getAuditInfo());
351 resp.setHeader("Last-Modified", lastModified);
352 // X-GSS-Metadata header
354 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
355 } catch (InsufficientPermissionsException e) {
356 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
359 // Get content length
360 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
361 // Special case for zero length files, which would cause a
362 // (silent) ISE when setting the output buffer size
363 if (contentLength == 0L)
367 ServletOutputStream ostream = null;
368 PrintWriter writer = null;
372 ostream = resp.getOutputStream();
373 } catch (IllegalStateException e) {
374 // If it fails, we try to get a Writer instead if we're
375 // trying to serve a text file
376 if ( contentType == null
377 || contentType.startsWith("text")
378 || contentType.endsWith("xml") )
379 writer = resp.getWriter();
385 || (ranges == null || ranges.isEmpty())
386 && req.getHeader("Range") == null
388 // Set the appropriate output headers
389 if (contentType != null) {
390 if (logger.isDebugEnabled())
391 logger.debug("contentType='" + contentType + "'");
392 resp.setContentType(contentType);
394 if (file != null && contentLength >= 0) {
395 if (logger.isDebugEnabled())
396 logger.debug("contentLength=" + contentLength);
397 if (contentLength < Integer.MAX_VALUE)
398 resp.setContentLength((int) contentLength);
400 // Set the content-length as String to be able to use a long
401 resp.setHeader("content-length", "" + contentLength);
404 InputStream renderResult = null;
407 // Serve the directory browser
409 renderResult = renderJson(user, folder);
410 } catch (InsufficientPermissionsException e) {
411 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
414 // Copy the input stream to our output stream (if requested)
417 resp.setBufferSize(output);
418 } catch (IllegalStateException e) {
422 if(file != null && needsContentDisposition(req))
423 resp.setHeader("Content-Disposition","attachment; filename=\""+file.getName()+"\"");
425 copy(file, renderResult, ostream, req, oldBody);
427 copy(file, renderResult, writer, req, oldBody);
428 if (file!=null) getService().updateAccounting(user, new Date(), contentLength);
429 } catch (ObjectNotFoundException e) {
430 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
432 } catch (InsufficientPermissionsException e) {
433 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
435 } catch (RpcException e) {
436 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
441 if (ranges == null || ranges.isEmpty())
443 // Partial content response.
444 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
446 if (ranges.size() == 1) {
447 Range range = (Range) ranges.get(0);
448 resp.addHeader("Content-Range", "bytes "
450 + "-" + range.end + "/"
452 long length = range.end - range.start + 1;
453 if (length < Integer.MAX_VALUE)
454 resp.setContentLength((int) length);
456 // Set the content-length as String to be able to use a long
457 resp.setHeader("content-length", "" + length);
459 if (contentType != null) {
460 if (logger.isDebugEnabled())
461 logger.debug("contentType='" + contentType + "'");
462 resp.setContentType(contentType);
467 resp.setBufferSize(output);
468 } catch (IllegalStateException e) {
473 copy(file, ostream, range, req, oldBody);
475 copy(file, writer, range, req, oldBody);
476 getService().updateAccounting(user, new Date(), contentLength);
477 } catch (ObjectNotFoundException e) {
478 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
480 } catch (InsufficientPermissionsException e) {
481 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
483 } catch (RpcException e) {
484 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
489 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
492 resp.setBufferSize(output);
493 } catch (IllegalStateException e) {
498 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
500 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
501 getService().updateAccounting(user, new Date(), contentLength);
502 } catch (ObjectNotFoundException e) {
503 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
505 } catch (InsufficientPermissionsException e) {
506 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
508 } catch (RpcException e) {
509 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
518 * Determines whether the user agent needs the Content-Disposition
519 * header to be set, in order to properly download a file.
521 * @param req the HTTP request
522 * @return true if the Content-Disposition HTTP header must be set
524 private boolean needsContentDisposition(HttpServletRequest req) {
525 if (req.getHeader("user-agent").contains("MSIE"))
531 * Sends a progress update on the amount of bytes received until now for
532 * a file that the current user is currently uploading.
534 * @param req the HTTP request
535 * @param resp the HTTP response
536 * @param parameter the value for the progress request parameter
537 * @param user the current user
538 * @param file the file being uploaded, or null if the request is about a new file
539 * @throws IOException if an I/O error occurs
541 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
542 String parameter, User user, FileHeaderDTO file) throws IOException {
543 String filename = file == null ? parameter : file.getName();
545 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
546 if (status == null) {
547 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
550 JSONObject json = new JSONObject();
551 json.put("bytesUploaded", status.getBytesUploaded()).
552 put("bytesTotal", status.getFileSize());
553 sendJson(req, resp, json.toString());
555 // Workaround for IE's broken caching behavior.
556 resp.setHeader("Expires", "-1");
558 } catch (RpcException e) {
559 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
561 } catch (JSONException e) {
562 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
568 * Server a POST request to create/modify a file or folder.
570 * @param req the HTTP request
571 * @param resp the HTTP response
572 * @exception IOException if an input/output error occurs
574 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
575 boolean authDeferred = getAuthDeferred(req);
576 if (!authDeferred && req.getParameterMap().size() > 1) {
577 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
580 String path = getInnerPath(req, PATH_FILES);
581 path = path.endsWith("/")? path: path + '/';
583 path = URLDecoder.decode(path, "UTF-8");
584 } catch (IllegalArgumentException e) {
585 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
588 // We only defer authenticating multipart POST requests.
590 if (!ServletFileUpload.isMultipartContent(req)) {
591 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
594 handleMultipart(req, resp, path);
598 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
599 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
600 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
601 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
602 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
603 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
604 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
606 if (newName != null) {
608 newName = URLDecoder.decode(newName, "UTF-8");
609 } catch (IllegalArgumentException e) {
610 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
613 createFolder(req, resp, path, newName);
614 } else if (hasUpdateParam)
615 updateResource(req, resp, path);
616 else if (hasTrashParam)
617 trashResource(req, resp, path);
618 else if (hasRestoreParam)
619 restoreResource(req, resp, path);
620 else if (copyTo != null)
621 copyResource(req, resp, path, copyTo);
622 else if (moveTo != null)
623 moveResource(req, resp, path, moveTo);
624 else if (restoreVersion != null)
625 restoreVersion(req, resp, path, restoreVersion);
627 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
631 * Restores a previous version for a file.
633 * @param req the HTTP request
634 * @param resp the HTTP response
635 * @param path the resource path
636 * @param version the version number to restore
637 * @throws IOException if an I/O error occurs
639 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
640 User user = getUser(req);
641 User owner = getOwner(req);
642 Object resource = null;
644 resource = getService().getResourceAtPath(owner.getId(), path, true);
645 } catch (ObjectNotFoundException e) {
646 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
648 } catch (RpcException e) {
649 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
652 if (resource instanceof FolderDTO) {
653 resp.sendError(HttpServletResponse.SC_CONFLICT);
658 FileHeaderDTO file = (FileHeaderDTO) resource;
659 int oldVersion = Integer.parseInt(version);
660 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
661 } catch (InsufficientPermissionsException e) {
662 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
663 } catch (ObjectNotFoundException e) {
664 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
665 } catch (RpcException e) {
666 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
667 } catch (GSSIOException e) {
668 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
669 } catch (QuotaExceededException e) {
670 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
671 } catch (NumberFormatException e) {
672 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
677 * A method for handling multipart POST requests for uploading
678 * files from browser-based JavaScript clients.
680 * @param request the HTTP request
681 * @param response the HTTP response
682 * @param path the resource path
683 * @throws IOException in case an error occurs writing to the
686 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
687 if (logger.isDebugEnabled())
688 logger.debug("Multipart POST for resource: " + path);
690 User owner = getOwner(request);
691 boolean exists = true;
692 Object resource = null;
693 FileHeaderDTO file = null;
695 resource = getService().getResourceAtPath(owner.getId(), path, false);
696 } catch (ObjectNotFoundException e) {
698 } catch (RpcException e) {
699 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
704 if (resource instanceof FileHeaderDTO) {
705 file = (FileHeaderDTO) resource;
706 if (file.isDeleted()) {
707 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
711 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
715 FolderDTO folder = null;
717 String parentPath = null;
719 parentPath = getParentPath(path);
720 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
721 } catch (ObjectNotFoundException e) {
722 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
724 } catch (RpcException e) {
725 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
728 if (!(parent instanceof FolderDTO)) {
729 response.sendError(HttpServletResponse.SC_CONFLICT);
732 folder = (FolderDTO) parent;
733 String fileName = getLastElement(path);
735 FileItemIterator iter;
736 File uploadedFile = null;
738 // Create a new file upload handler.
739 ServletFileUpload upload = new ServletFileUpload();
740 StatusProgressListener progressListener = new StatusProgressListener(getService());
741 upload.setProgressListener(progressListener);
742 iter = upload.getItemIterator(request);
743 String dateParam = null;
745 while (iter.hasNext()) {
746 FileItemStream item = iter.next();
747 String name = item.getFieldName();
748 InputStream stream = item.openStream();
749 if (item.isFormField()) {
750 final String value = Streams.asString(stream);
751 if (name.equals(DATE_PARAMETER))
753 else if (name.equals(AUTHORIZATION_PARAMETER))
756 if (logger.isDebugEnabled())
757 logger.debug(name + ":" + value);
759 // Fetch the timestamp used to guard against replay attacks.
760 if (dateParam == null) {
761 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
767 timestamp = DateUtil.parseDate(dateParam).getTime();
768 } catch (DateParseException e) {
769 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
772 if (!isTimeValid(timestamp)) {
773 response.sendError(HttpServletResponse.SC_FORBIDDEN);
777 // Fetch the Authorization parameter and find the user specified in it.
779 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
782 String[] authParts = auth.split(" ");
783 if (authParts.length != 2) {
784 response.sendError(HttpServletResponse.SC_FORBIDDEN);
787 String username = authParts[0];
788 String signature = authParts[1];
791 user = getService().findUser(username);
792 } catch (RpcException e) {
793 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
797 response.sendError(HttpServletResponse.SC_FORBIDDEN);
800 request.setAttribute(USER_ATTRIBUTE, user);
802 // Remove the servlet path from the request URI.
803 String p = request.getRequestURI();
804 String servletPath = request.getContextPath() + request.getServletPath();
805 p = p.substring(servletPath.length());
806 // Validate the signature in the Authorization parameter.
807 String data = request.getMethod() + dateParam + p;
808 if (!isSignatureValid(signature, user, data)) {
809 response.sendError(HttpServletResponse.SC_FORBIDDEN);
813 progressListener.setUserId(user.getId());
814 progressListener.setFilename(fileName);
815 String contentType = item.getContentType();
818 uploadedFile = getService().uploadFile(stream, user.getId());
819 } catch (IOException ex) {
820 throw new GSSIOException(ex, false);
822 FileHeaderDTO fileDTO = null;
824 fileDTO = getService().createFile(user.getId(), folder.getId(), fileName, contentType, uploadedFile);
826 fileDTO = getService().updateFileContents(user.getId(), file.getId(), contentType, uploadedFile);
827 getService().updateAccounting(user, new Date(), fileDTO.getFileSize());
828 getService().removeFileUploadProgress(user.getId(), fileName);
831 // We can't return 204 here since GWT's onSubmitComplete won't fire.
832 response.setContentType("text/html");
833 response.getWriter().print("<pre></pre>");
834 } catch (FileUploadException e) {
835 String error = "Error while uploading file";
836 logger.error(error, e);
837 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
838 } catch (GSSIOException e) {
839 if (uploadedFile != null && uploadedFile.exists())
840 uploadedFile.delete();
841 String error = "Error while uploading file";
843 logger.error(error, e);
845 logger.debug(error, e);
846 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
847 } catch (DuplicateNameException e) {
848 if (uploadedFile != null && uploadedFile.exists())
849 uploadedFile.delete();
850 String error = "The specified file name already exists in this folder";
851 logger.error(error, e);
852 response.sendError(HttpServletResponse.SC_CONFLICT, error);
854 } catch (InsufficientPermissionsException e) {
855 if (uploadedFile != null && uploadedFile.exists())
856 uploadedFile.delete();
857 String error = "You don't have the necessary permissions";
858 logger.error(error, e);
859 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
861 } catch (QuotaExceededException e) {
862 if (uploadedFile != null && uploadedFile.exists())
863 uploadedFile.delete();
864 String error = "Not enough free space available";
865 if (logger.isDebugEnabled())
866 logger.debug(error, e);
867 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
869 } catch (ObjectNotFoundException e) {
870 if (uploadedFile != null && uploadedFile.exists())
871 uploadedFile.delete();
872 String error = "A specified object was not found";
873 logger.error(error, e);
874 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
875 } catch (RpcException e) {
876 if (uploadedFile != null && uploadedFile.exists())
877 uploadedFile.delete();
878 String error = "An error occurred while communicating with the service";
879 logger.error(error, e);
880 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
884 private String getBackupFilename(FileHeaderDTO file, String filename){
885 List<FileHeaderDTO> deletedFiles = new ArrayList<FileHeaderDTO>();
887 deletedFiles = getService().getDeletedFiles(file.getOwner().getId());
889 catch(ObjectNotFoundException e){
891 } catch (RpcException e) {
894 List<FileHeaderDTO> filesInSameFolder = new ArrayList<FileHeaderDTO>();
895 for(FileHeaderDTO deleted : deletedFiles)
896 if(deleted.getFolder().getId().equals(file.getFolder().getId()))
897 filesInSameFolder.add(deleted);
899 String filenameToCheck = filename;
900 for(FileHeaderDTO same : filesInSameFolder)
901 if(same.getName().startsWith(filename)){
902 String toCheck=same.getName().substring(filename.length(),same.getName().length());
903 if(toCheck.startsWith(" ")){
906 test = Integer.valueOf(toCheck.replace(" ",""));
908 catch(NumberFormatException e){
909 //do nothing since string is not a number
916 return filename+" "+i;
920 * Move the resource in the specified path to the specified destination.
922 * @param req the HTTP request
923 * @param resp the HTTP response
924 * @param path the path of the resource
925 * @param moveTo the destination of the move procedure
926 * @throws IOException if an input/output error occurs
928 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
929 User user = getUser(req);
930 User owner = getOwner(req);
931 Object resource = null;
933 resource = getService().getResourceAtPath(owner.getId(), path, true);
934 } catch (ObjectNotFoundException e) {
935 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
937 } catch (RpcException e) {
938 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
942 String destination = null;
943 User destOwner = null;
944 boolean exists = true;
946 destination = getDestinationPath(req, encodePath(moveTo));
947 destination = URLDecoder.decode(destination, "UTF-8");
948 destOwner = getDestinationOwner(req);
949 getService().getResourceAtPath(destOwner.getId(), destination, true);
950 } catch (ObjectNotFoundException e) {
952 } catch (URISyntaxException e) {
953 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
955 } catch (RpcException e) {
956 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
960 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
965 if (resource instanceof FolderDTO) {
966 FolderDTO folder = (FolderDTO) resource;
967 getService().moveFolderToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
969 FileHeaderDTO file = (FileHeaderDTO) resource;
970 getService().moveFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
972 } catch (InsufficientPermissionsException e) {
973 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
974 } catch (ObjectNotFoundException e) {
975 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
976 } catch (RpcException e) {
977 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
978 } catch (DuplicateNameException e) {
979 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
980 } catch (GSSIOException e) {
981 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
982 } catch (QuotaExceededException e) {
983 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
988 * Copy the resource in the specified path to the specified destination.
990 * @param req the HTTP request
991 * @param resp the HTTP response
992 * @param path the path of the resource
993 * @param copyTo the destination of the copy procedure
994 * @throws IOException if an input/output error occurs
996 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
997 User user = getUser(req);
998 User owner = getOwner(req);
999 Object resource = null;
1001 resource = getService().getResourceAtPath(owner.getId(), path, true);
1002 } catch (ObjectNotFoundException e) {
1003 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1005 } catch (RpcException e) {
1006 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1010 String destination = null;
1011 User destOwner = null;
1012 boolean exists = true;
1014 destination = getDestinationPath(req, encodePath(copyTo));
1015 destination = URLDecoder.decode(destination, "UTF-8");
1016 destOwner = getDestinationOwner(req);
1017 getService().getResourceAtPath(destOwner.getId(), destination, true);
1018 } catch (ObjectNotFoundException e) {
1020 } catch (URISyntaxException e) {
1021 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1023 } catch (RpcException e) {
1024 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1028 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1033 if (resource instanceof FolderDTO) {
1034 FolderDTO folder = (FolderDTO) resource;
1035 getService().copyFolderStructureToPath(user.getId(), destOwner.getId(), folder.getId(), destination);
1037 FileHeaderDTO file = (FileHeaderDTO) resource;
1038 getService().copyFileToPath(user.getId(), destOwner.getId(), file.getId(), destination);
1040 } catch (InsufficientPermissionsException e) {
1041 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1042 } catch (ObjectNotFoundException e) {
1043 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1044 } catch (RpcException e) {
1045 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1046 } catch (DuplicateNameException e) {
1047 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1048 } catch (GSSIOException e) {
1049 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1050 } catch (QuotaExceededException e) {
1051 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1055 private String encodePath(String path) throws UnsupportedEncodingException{
1056 StringTokenizer str = new StringTokenizer(path, "/:", true);
1057 String result = new String();
1058 while(str.hasMoreTokens()){
1059 String token = str.nextToken();
1060 if(!token.equals("/") && !token.equals(":"))
1061 token = URLEncoder.encode(token,"UTF-8");
1062 result = result + token;
1067 * A helper method that extracts the relative resource path,
1068 * after removing the 'files' namespace.
1070 * @param req the HTTP request
1071 * @param path the specified path
1072 * @return the path relative to the root folder
1073 * @throws URISyntaxException
1074 * @throws RpcException in case an error occurs while communicating
1077 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException {
1078 URI uri = new URI(path);
1079 String dest = uri.getPath();
1080 // Remove the context path from the destination URI.
1081 String contextPath = req.getContextPath();
1082 if (!dest.startsWith(contextPath))
1083 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1084 dest = dest.substring(contextPath.length());
1085 // Remove the servlet path from the destination URI.
1086 String servletPath = req.getServletPath();
1087 if (!dest.startsWith(servletPath))
1088 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1089 dest = dest.substring(servletPath.length());
1090 // Strip the username part
1091 if (dest.length() < 2)
1092 throw new URISyntaxException(dest, "No username in the destination URI");
1093 int slash = dest.substring(1).indexOf('/');
1095 throw new URISyntaxException(dest, "No username in the destination URI");
1096 String owner = dest.substring(1, slash + 1);
1098 o = getService().findUser(owner);
1100 throw new URISyntaxException(dest, "User " + owner + " not found");
1102 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1103 dest = dest.substring(slash + 1);
1105 // Chop the resource namespace part
1106 dest = dest.substring(RequestHandler.PATH_FILES.length());
1108 dest = dest.endsWith("/")? dest: dest + '/';
1113 * Move the resource in the specified path to the trash bin.
1115 * @param req the HTTP request
1116 * @param resp the HTTP response
1117 * @param path the path of the resource
1118 * @throws IOException if an input/output error occurs
1120 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1121 User user = getUser(req);
1122 User owner = getOwner(req);
1123 Object resource = null;
1125 resource = getService().getResourceAtPath(owner.getId(), path, true);
1126 } catch (ObjectNotFoundException e) {
1127 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1129 } catch (RpcException e) {
1130 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1135 if (resource instanceof FolderDTO) {
1136 FolderDTO folder = (FolderDTO) resource;
1137 getService().moveFolderToTrash(user.getId(), folder.getId());
1139 FileHeaderDTO file = (FileHeaderDTO) resource;
1140 getService().moveFileToTrash(user.getId(), file.getId());
1142 } catch (InsufficientPermissionsException e) {
1143 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1144 } catch (ObjectNotFoundException e) {
1145 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1146 } catch (RpcException e) {
1147 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1152 * Restore the resource in the specified path from the trash bin.
1154 * @param req the HTTP request
1155 * @param resp the HTTP response
1156 * @param path the path of the resource
1157 * @throws IOException if an input/output error occurs
1159 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1160 User user = getUser(req);
1161 User owner = getOwner(req);
1162 Object resource = null;
1164 resource = getService().getResourceAtPath(owner.getId(), path, false);
1165 } catch (ObjectNotFoundException e) {
1166 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1168 } catch (RpcException e) {
1169 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1174 if (resource instanceof FolderDTO) {
1175 FolderDTO folder = (FolderDTO) resource;
1176 getService().removeFolderFromTrash(user.getId(), folder.getId());
1178 FileHeaderDTO file = (FileHeaderDTO) resource;
1179 getService().removeFileFromTrash(user.getId(), file.getId());
1181 } catch (InsufficientPermissionsException e) {
1182 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1183 } catch (ObjectNotFoundException e) {
1184 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1185 } catch (RpcException e) {
1186 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1191 * Update the resource in the specified path.
1193 * @param req the HTTP request
1194 * @param resp the HTTP response
1195 * @param path the path of the resource
1196 * @throws IOException if an input/output error occurs
1198 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1199 User user = getUser(req);
1200 User owner = getOwner(req);
1201 Object resource = null;
1203 resource = getService().getResourceAtPath(owner.getId(), path, false);
1204 } catch (ObjectNotFoundException e) {
1205 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1207 } catch (RpcException e) {
1208 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1211 //use utf-8 encoding for reading request
1212 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1213 StringBuffer input = new StringBuffer();
1215 JSONObject json = null;
1216 while ((line = reader.readLine()) != null)
1220 json = new JSONObject(input.toString());
1221 if (logger.isDebugEnabled())
1222 logger.debug("JSON update: " + json);
1223 if (resource instanceof FolderDTO) {
1224 FolderDTO folder = (FolderDTO) resource;
1225 String name = json.optString("name");
1226 if (!name.isEmpty()){
1228 name = URLDecoder.decode(name, "UTF-8");
1229 } catch (IllegalArgumentException e) {
1230 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1233 getService().modifyFolder(user.getId(), folder.getId(), name);
1234 FolderDTO folderUpdated = getService().getFolder(user.getId(), folder.getId());
1235 String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1236 String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1237 parentUrl = parentUrl.replaceAll(fpath, "");
1238 if(!parentUrl.endsWith("/"))
1239 parentUrl = parentUrl+"/";
1240 parentUrl = parentUrl+folderUpdated.getOwner().getUsername()+PATH_FILES+folderUpdated.getPath();
1241 resp.getWriter().println(parentUrl);
1244 JSONArray permissions = json.optJSONArray("permissions");
1245 if (permissions != null) {
1246 Set<PermissionDTO> perms = parsePermissions(user, permissions);
1247 getService().setFolderPermissions(user.getId(), folder.getId(), perms);
1250 FileHeaderDTO file = (FileHeaderDTO) resource;
1252 if (json.opt("name") != null)
1253 name = json.optString("name");
1254 JSONArray tagset = json.optJSONArray("tags");
1256 StringBuffer t = new StringBuffer();
1257 if (tagset != null) {
1258 for (int i = 0; i < tagset.length(); i++)
1259 t.append(tagset.getString(i) + ',');
1260 tags = t.toString();
1262 if (name != null || tags != null)
1263 getService().updateFile(user.getId(), file.getId(), name, tags);
1265 JSONArray permissions = json.optJSONArray("permissions");
1266 Set<PermissionDTO> perms = null;
1267 if (permissions != null)
1268 perms = parsePermissions(user, permissions);
1269 Boolean readForAll = null;
1270 if (json.opt("readForAll") != null)
1271 readForAll = json.optBoolean("readForAll");
1272 if (perms != null || readForAll != null)
1273 getService().setFilePermissions(user.getId(), file.getId(), readForAll, perms);
1275 if (json.opt("versioned") != null) {
1276 boolean versioned = json.getBoolean("versioned");
1277 getService().toggleFileVersioning(user.getId(), file.getId(), versioned);
1280 } catch (JSONException e) {
1281 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1282 } catch (InsufficientPermissionsException e) {
1283 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1284 } catch (ObjectNotFoundException e) {
1285 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1286 } catch (DuplicateNameException e) {
1287 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1288 } catch (RpcException e) {
1289 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1294 * Helper method to convert a JSON array of permissions into a set of
1295 * PermissionDTO objects.
1297 * @param user the current user
1298 * @param permissions the JSON array to parse
1299 * @return the parsed set of permissions
1300 * @throws JSONException if there was an error parsing the JSON object
1301 * @throws RpcException if there was an error communicating with the EJB
1302 * @throws ObjectNotFoundException if the user could not be found
1304 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1305 throws JSONException, RpcException, ObjectNotFoundException {
1306 if (permissions == null)
1308 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1309 for (int i = 0; i < permissions.length(); i++) {
1310 JSONObject j = permissions.getJSONObject(i);
1311 PermissionDTO perm = new PermissionDTO();
1312 perm.setModifyACL(j.optBoolean("modifyACL"));
1313 perm.setRead(j.optBoolean("read"));
1314 perm.setWrite(j.optBoolean("write"));
1315 String permUser = j.optString("user");
1316 if (!permUser.isEmpty()) {
1317 User u = getService().findUser(permUser);
1319 throw new ObjectNotFoundException("User " + permUser + " not found");
1320 perm.setUser(u.getDTO());
1322 String permGroup = j.optString("group");
1323 if (!permGroup.isEmpty()) {
1324 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1327 if (permUser.isEmpty() && permGroup.isEmpty() ||
1328 permUser.isEmpty() && permGroup.isEmpty())
1329 throw new JSONException("A permission must correspond to either a user or a group");
1336 * Creates a new folder with the specified name under the folder in the provided path.
1338 * @param req the HTTP request
1339 * @param resp the HTTP response
1340 * @param path the parent folder path
1341 * @param folderName the name of the new folder
1342 * @throws IOException if an input/output error occurs
1344 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, String folderName) throws IOException {
1345 if (logger.isDebugEnabled())
1346 logger.debug("Creating folder " + folderName + " in '" + path);
1348 User user = getUser(req);
1349 User owner = getOwner(req);
1350 boolean exists = true;
1352 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1353 } catch (ObjectNotFoundException e) {
1355 } catch (RpcException e) {
1356 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1361 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1362 ", " + METHOD_HEAD);
1363 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1369 parent = getService().getResourceAtPath(owner.getId(), path, true);
1370 } catch (ObjectNotFoundException e) {
1371 resp.sendError(HttpServletResponse.SC_CONFLICT);
1373 } catch (RpcException e) {
1374 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1378 if (parent instanceof FolderDTO) {
1379 FolderDTO folder = (FolderDTO) parent;
1380 getService().createFolder(user.getId(), folder.getId(), folderName);
1381 String newResource = getContextPath(req, true) + folderName;
1382 resp.setHeader("Location", newResource);
1383 resp.setContentType("text/plain");
1384 PrintWriter out = resp.getWriter();
1385 out.println(newResource);
1387 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1390 } catch (DuplicateNameException e) {
1391 resp.sendError(HttpServletResponse.SC_CONFLICT);
1393 } catch (InsufficientPermissionsException e) {
1394 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1396 } catch (ObjectNotFoundException e) {
1397 resp.sendError(HttpServletResponse.SC_CONFLICT);
1399 } catch (RpcException e) {
1400 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1403 resp.setStatus(HttpServletResponse.SC_CREATED);
1409 * @throws IOException
1410 * @throws FileNotFoundException
1412 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1413 String path = getInnerPath(req, PATH_FILES);
1414 if (logger.isDebugEnabled())
1415 logger.debug("Updating resource: " + path);
1417 User user = getUser(req);
1418 User owner = getOwner(req);
1419 boolean exists = true;
1420 Object resource = null;
1421 FileHeaderDTO file = null;
1423 resource = getService().getResourceAtPath(owner.getId(), path, false);
1424 } catch (ObjectNotFoundException e) {
1426 } catch (RpcException e) {
1427 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1432 if (resource instanceof FileHeaderDTO)
1433 file = (FileHeaderDTO) resource;
1435 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1438 boolean result = true;
1440 // Temporary content file used to support partial PUT.
1441 File contentFile = null;
1443 Range range = parseContentRange(req, resp);
1445 InputStream resourceInputStream = null;
1447 // Append data specified in ranges to existing content for this
1448 // resource - create a temporary file on the local filesystem to
1449 // perform this operation.
1450 // Assume just one range is specified for now
1451 if (range != null) {
1453 contentFile = executePartialPut(req, range, path);
1454 } catch (RpcException e) {
1455 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1457 } catch (ObjectNotFoundException e) {
1458 resp.sendError(HttpServletResponse.SC_CONFLICT);
1460 } catch (InsufficientPermissionsException e) {
1461 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1464 resourceInputStream = new FileInputStream(contentFile);
1466 resourceInputStream = req.getInputStream();
1469 FolderDTO folder = null;
1470 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1471 if (!(parent instanceof FolderDTO)) {
1472 resp.sendError(HttpServletResponse.SC_CONFLICT);
1475 folder = (FolderDTO) parent;
1476 String name = getLastElement(path);
1477 String mimeType = context.getMimeType(name);
1478 // FIXME: Add attributes
1479 FileHeaderDTO fileDTO = null;
1481 fileDTO = getService().updateFileContents(user.getId(), file.getId(), mimeType, resourceInputStream);
1483 fileDTO = getService().createFile(user.getId(), folder.getId(), name, mimeType, resourceInputStream);
1484 getService().updateAccounting(user, new Date(), fileDTO.getFileSize());
1485 getService().removeFileUploadProgress(user.getId(), file.getName());
1486 } catch(ObjectNotFoundException e) {
1488 } catch (RpcException e) {
1489 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1491 } catch (IOException e) {
1492 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1494 } catch (GSSIOException e) {
1495 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1497 } catch (DuplicateNameException e) {
1498 resp.sendError(HttpServletResponse.SC_CONFLICT);
1500 } catch (InsufficientPermissionsException e) {
1501 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1503 } catch (QuotaExceededException e) {
1504 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1510 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1512 resp.setStatus(HttpServletResponse.SC_CREATED);
1514 resp.sendError(HttpServletResponse.SC_CONFLICT);
1518 * Delete a resource.
1520 * @param req The servlet request we are processing
1521 * @param resp The servlet response we are processing
1522 * @throws IOException if the response cannot be sent
1524 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1525 String path = getInnerPath(req, PATH_FILES);
1526 if (logger.isDebugEnabled())
1527 logger.debug("Deleting resource '" + path);
1528 path = URLDecoder.decode(path, "UTF-8");
1529 User user = getUser(req);
1530 User owner = getOwner(req);
1531 boolean exists = true;
1532 Object object = null;
1534 object = getService().getResourceAtPath(owner.getId(), path, false);
1535 } catch (ObjectNotFoundException e) {
1537 } catch (RpcException e) {
1538 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1543 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1547 FolderDTO folder = null;
1548 FileHeaderDTO file = null;
1549 if (object instanceof FolderDTO)
1550 folder = (FolderDTO) object;
1552 file = (FileHeaderDTO) object;
1556 getService().deleteFile(user.getId(), file.getId());
1557 } catch (InsufficientPermissionsException e) {
1558 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1560 } catch (ObjectNotFoundException e) {
1561 // Although we had already found the object, it was
1562 // probably deleted from another thread.
1563 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1565 } catch (RpcException e) {
1566 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1569 else if (folder != null)
1571 getService().deleteFolder(user.getId(), folder.getId());
1572 } catch (InsufficientPermissionsException e) {
1573 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1575 } catch (ObjectNotFoundException e) {
1576 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1578 } catch (RpcException e) {
1579 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1582 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1587 * Return an InputStream to a JSON representation of the contents
1588 * of this directory.
1590 * @param user the user that made the request
1591 * @param folder the specified directory
1592 * @return an input stream with the rendered contents
1593 * @throws IOException if the response cannot be sent
1594 * @throws ServletException
1595 * @throws InsufficientPermissionsException if the user does not have
1596 * the necessary privileges to read the directory
1598 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1599 ServletException, InsufficientPermissionsException {
1600 JSONObject json = new JSONObject();
1602 json.put("name", folder.getName()).
1603 put("owner", folder.getOwner().getUsername()).
1604 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1605 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1606 put("deleted", folder.isDeleted());
1607 if (folder.getParent() != null) {
1608 JSONObject j = new JSONObject();
1609 j.put("uri", getApiRoot() + folder.getParent().getURI());
1610 j.put("name", folder.getParent().getName());
1611 json.put("parent", j);
1613 if (folder.getAuditInfo().getModifiedBy() != null)
1614 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1615 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1616 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1617 for (FolderDTO f: folder.getSubfolders())
1618 if (!f.isDeleted()) {
1619 JSONObject j = new JSONObject();
1620 j.put("name", f.getName()).
1621 put("uri", getApiRoot() + f.getURI());
1624 json.put("folders", subfolders);
1625 List<JSONObject> files = new ArrayList<JSONObject>();
1626 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1627 for (FileHeaderDTO f: fileHeaders) {
1628 JSONObject j = new JSONObject();
1629 j.put("name", f.getName()).
1630 put("owner", f.getOwner().getUsername()).
1631 put("deleted", f.isDeleted()).
1632 put("version", f.getVersion()).
1633 put("content", f.getMimeType()).
1634 put("size", f.getFileSize()).
1635 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1636 put("path", f.getFolder().getPath()).
1637 put("uri", getApiRoot() + f.getURI());
1641 json.put("files", files);
1642 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1643 json.put("permissions", renderJson(perms));
1644 } catch (JSONException e) {
1645 throw new ServletException(e);
1646 } catch (ObjectNotFoundException e) {
1647 throw new ServletException(e);
1648 } catch (RpcException e) {
1649 throw new ServletException(e);
1652 // Prepare a writer to a buffered area
1653 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1654 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1655 PrintWriter writer = new PrintWriter(osWriter);
1657 // Return an input stream to the underlying bytes
1658 writer.write(json.toString());
1660 return new ByteArrayInputStream(stream.toByteArray());
1664 * Return a String with a JSON representation of the metadata
1665 * of the specified file. If an old file body is provided, then
1666 * the metadata of that particular version will be returned.
1668 * @param user the user that made the request
1669 * @param file the specified file header
1670 * @param oldBody the version number
1671 * @return the JSON-encoded file
1672 * @throws ServletException
1673 * @throws InsufficientPermissionsException if the user does not have
1674 * the necessary privileges to read the directory
1676 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
1677 throws ServletException, InsufficientPermissionsException {
1678 JSONObject json = new JSONObject();
1680 // Need to encode file name in order to properly display it in the web client.
1681 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
1682 put("owner", file.getOwner().getUsername()).
1683 put("versioned", file.isVersioned()).
1684 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
1685 put("readForAll", file.isReadForAll()).
1686 put("tags", renderJson(file.getTags())).
1687 put("path", file.getFolder().getPath()).
1688 put("uri", getApiRoot() + file.getURI()).
1689 put("deleted", file.isDeleted());
1690 JSONObject j = new JSONObject();
1691 j.put("uri", getApiRoot() + file.getFolder().getURI()).
1692 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
1693 json.put("folder", j);
1694 if (oldBody != null)
1695 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
1696 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
1697 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
1698 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
1699 put("content", oldBody.getMimeType()).
1700 put("size", oldBody.getFileSize());
1702 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
1703 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
1704 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
1705 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
1706 put("content", file.getMimeType()).
1707 put("size", file.getFileSize());
1708 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
1709 json.put("permissions", renderJson(perms));
1710 } catch (JSONException e) {
1711 throw new ServletException(e);
1712 } catch (ObjectNotFoundException e) {
1713 throw new ServletException(e);
1714 } catch (RpcException e) {
1715 throw new ServletException(e);
1716 } catch (UnsupportedEncodingException e) {
1717 throw new ServletException(e);
1720 return json.toString();
1724 * Return a String with a JSON representation of the
1725 * specified set of permissions.
1727 * @param permissions the set of permissions
1728 * @return the JSON-encoded object
1729 * @throws JSONException
1730 * @throws UnsupportedEncodingException
1732 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
1733 JSONArray perms = new JSONArray();
1734 for (PermissionDTO p: permissions) {
1735 JSONObject permission = new JSONObject();
1736 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
1737 if (p.getUser() != null)
1738 permission.put("user", p.getUser().getUsername());
1739 if (p.getGroup() != null)
1740 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
1741 perms.put(permission);
1747 * Return a String with a JSON representation of the
1748 * specified collection of tags.
1750 * @param tags the collection of tags
1751 * @return the JSON-encoded object
1752 * @throws JSONException
1753 * @throws UnsupportedEncodingException
1755 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
1756 JSONArray tagArray = new JSONArray();
1757 for (String t: tags)
1758 tagArray.put(URLEncoder.encode(t,"UTF-8"));
1763 * Retrieves the user who owns the destination namespace, for a
1764 * copy or move request.
1766 * @param req the HTTP request
1767 * @return the owner of the namespace
1769 protected User getDestinationOwner(HttpServletRequest req) {
1770 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
1774 * A helper inner class for updating the progress status of a file upload.
1778 public static class StatusProgressListener implements ProgressListener {
1779 private int percentLogged = 0;
1780 private long bytesTransferred = 0;
1782 private long fileSize = -100;
1784 private long tenKBRead = -1;
1786 private Long userId;
1788 private String filename;
1790 private ExternalAPI service;
1792 public StatusProgressListener(ExternalAPI aService) {
1797 * Modify the userId.
1799 * @param aUserId the userId to set
1801 public void setUserId(Long aUserId) {
1806 * Modify the filename.
1808 * @param aFilename the filename to set
1810 public void setFilename(String aFilename) {
1811 filename = aFilename;
1814 public void update(long bytesRead, long contentLength, int items) {
1815 //monitoring per percent of bytes uploaded
1816 bytesTransferred = bytesRead;
1817 if (fileSize != contentLength)
1818 fileSize = contentLength;
1819 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
1821 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
1822 if (percent != percentLogged){
1823 percentLogged = percent;
1825 if (userId != null && filename != null)
1826 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
1827 } catch (ObjectNotFoundException e) {
1828 // Swallow the exception since it is going to be caught
1829 // by previously called methods