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 static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22 import gr.ebs.gss.client.exceptions.DuplicateNameException;
23 import gr.ebs.gss.client.exceptions.GSSIOException;
24 import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
25 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
26 import gr.ebs.gss.client.exceptions.QuotaExceededException;
27 import gr.ebs.gss.client.exceptions.RpcException;
28 import gr.ebs.gss.server.Login;
29 import gr.ebs.gss.server.domain.FileUploadStatus;
30 import gr.ebs.gss.server.domain.User;
31 import gr.ebs.gss.server.domain.dto.FileBodyDTO;
32 import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
33 import gr.ebs.gss.server.domain.dto.FolderDTO;
34 import gr.ebs.gss.server.domain.dto.GroupDTO;
35 import gr.ebs.gss.server.domain.dto.PermissionDTO;
36 import gr.ebs.gss.server.ejb.ExternalAPI;
37 import gr.ebs.gss.server.ejb.TransactionHelper;
38 import gr.ebs.gss.server.webdav.Range;
39 import gr.ebs.gss.server.webdav.RequestUtil;
41 import java.io.BufferedReader;
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.InputStreamReader;
50 import java.io.OutputStreamWriter;
51 import java.io.PrintWriter;
52 import java.io.UnsupportedEncodingException;
54 import java.net.URISyntaxException;
55 import java.net.URLDecoder;
56 import java.net.URLEncoder;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.Date;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.List;
65 import java.util.StringTokenizer;
66 import java.util.concurrent.Callable;
68 import javax.servlet.ServletContext;
69 import javax.servlet.ServletException;
70 import javax.servlet.ServletOutputStream;
71 import javax.servlet.http.Cookie;
72 import javax.servlet.http.HttpServletRequest;
73 import javax.servlet.http.HttpServletResponse;
75 import org.apache.commons.codec.binary.Base64;
76 import org.apache.commons.fileupload.FileItemIterator;
77 import org.apache.commons.fileupload.FileItemStream;
78 import org.apache.commons.fileupload.FileUploadException;
79 import org.apache.commons.fileupload.ProgressListener;
80 import org.apache.commons.fileupload.servlet.ServletFileUpload;
81 import org.apache.commons.fileupload.util.Streams;
82 import org.apache.commons.httpclient.util.DateParseException;
83 import org.apache.commons.httpclient.util.DateUtil;
84 import org.apache.commons.logging.Log;
85 import org.apache.commons.logging.LogFactory;
86 import org.json.JSONArray;
87 import org.json.JSONException;
88 import org.json.JSONObject;
92 * A class that handles operations on the 'files' namespace.
96 public class FilesHandler extends RequestHandler {
98 * The request parameter name for fetching a different version.
100 private static final String VERSION_PARAM = "version";
103 * The request attribute containing the owner of the destination URI
104 * in a copy or move request.
106 private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner";
108 private static final int TRACK_PROGRESS_PERCENT = 5;
111 * The form parameter name that contains the signature in a browser POST upload.
113 private static final String AUTHORIZATION_PARAMETER = "Authorization";
116 * The form parameter name that contains the date in a browser POST upload.
118 private static final String DATE_PARAMETER = "Date";
121 * The request parameter name for making an upload progress request.
123 private static final String PROGRESS_PARAMETER = "progress";
126 * The request parameter name for restoring a previous version of a file.
128 private static final String RESTORE_VERSION_PARAMETER = "restoreVersion";
133 private static Log logger = LogFactory.getLog(FilesHandler.class);
136 * The servlet context provided by the call site.
138 private ServletContext context;
141 * The style sheet for displaying the directory listings.
143 private static final String GSS_CSS = "H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " + "H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " + "H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " + "BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " + "B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " + "P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}" + "A {color : black;}" + "A.name {color : black;}" + "HR {color : #525D76;}";
147 * @param servletContext
149 public FilesHandler(ServletContext servletContext) {
150 context = servletContext;
153 private void updateAccounting(final User user, final Date date, final long bandwidthDiff) {
155 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
157 public Void call() throws Exception {
158 getService().updateAccounting(user, date, bandwidthDiff);
162 } catch (RuntimeException e) {
164 } catch (Exception e) {
165 // updateAccounting() doesn't throw any checked exceptions
171 * Serve the specified resource, optionally including the data content.
173 * @param req The servlet request we are processing
174 * @param resp The servlet response we are creating
175 * @param content Should the content be included?
177 * @exception IOException if an input/output error occurs
178 * @exception ServletException if a servlet-specified error occurs
179 * @throws RpcException
180 * @throws InsufficientPermissionsException
181 * @throws ObjectNotFoundException
184 protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content)
185 throws IOException, ServletException {
186 boolean authDeferred = getAuthDeferred(req);
187 String path = getInnerPath(req, PATH_FILES);
191 path = URLDecoder.decode(path, "UTF-8");
192 } catch (IllegalArgumentException e) {
193 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
196 String progress = req.getParameter(PROGRESS_PARAMETER);
198 if (logger.isDebugEnabled())
200 logger.debug("Serving resource '" + path + "' headers and data");
202 logger.debug("Serving resource '" + path + "' headers only");
204 User user = getUser(req);
205 User owner = getOwner(req);
206 if (user == null) user = owner;
207 boolean exists = true;
208 Object resource = null;
209 FileHeaderDTO file = null;
210 FolderDTO folder = null;
212 resource = getService().getResourceAtPath(owner.getId(), path, false);
213 } catch (ObjectNotFoundException e) {
215 } catch (RpcException e) {
216 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
222 // We do not want to leak information if the request
223 // was not authenticated.
224 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
227 // A request for upload progress.
228 if (progress != null && content) {
229 serveProgress(req, resp, progress, user, null);
233 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
237 if (resource instanceof FolderDTO)
238 folder = (FolderDTO) resource;
240 file = (FileHeaderDTO) resource;
242 // Now it's time to perform the deferred authentication check.
243 // Since regular signature checking was already performed,
244 // we need to check the read-all flag or the signature-in-parameters.
246 if (file != null && !file.isReadForAll() && content) {
247 // Check for GET with the signature in the request parameters.
248 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
249 String dateParam = req.getParameter(DATE_PARAMETER);
250 if (auth == null || dateParam == null) {
251 // Check for a valid authentication cookie.
252 if (req.getCookies() != null) {
253 boolean found = false;
254 for (Cookie cookie : req.getCookies())
255 if (Login.AUTH_COOKIE.equals(cookie.getName())) {
256 String cookieauth = cookie.getValue();
257 int sepIndex = cookieauth.indexOf(Login.COOKIE_SEPARATOR);
258 if (sepIndex == -1) {
259 handleAuthFailure(req, resp);
262 String username = URLDecoder.decode(cookieauth.substring(0, sepIndex), "US-ASCII");
265 user = getService().findUser(username);
266 } catch (RpcException e) {
267 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
271 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
274 req.setAttribute(USER_ATTRIBUTE, user);
275 String token = cookieauth.substring(sepIndex + 1);
276 if (user.getAuthToken() == null) {
277 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
280 if (!Arrays.equals(user.getAuthToken(), Base64.decodeBase64(token))) {
281 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
288 handleAuthFailure(req, resp);
292 handleAuthFailure(req, resp);
298 timestamp = DateUtil.parseDate(dateParam).getTime();
299 } catch (DateParseException e) {
300 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
303 if (!isTimeValid(timestamp)) {
304 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
308 // Fetch the Authorization parameter and find the user specified in it.
309 String[] authParts = auth.split(" ");
310 if (authParts.length != 2) {
311 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
314 String username = authParts[0];
315 String signature = authParts[1];
318 user = getService().findUser(username);
319 } catch (RpcException e) {
320 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
324 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
327 req.setAttribute(USER_ATTRIBUTE, user);
329 // Remove the servlet path from the request URI.
330 String p = req.getRequestURI();
331 String servletPath = req.getContextPath() + req.getServletPath();
332 p = p.substring(servletPath.length());
333 // Validate the signature in the Authorization parameter.
334 String data = req.getMethod() + dateParam + p;
335 if (!isSignatureValid(signature, user, data)) {
336 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
340 } else if (file != null && !file.isReadForAll() || folder != null && !folder.isReadForAll()) {
341 // Check for a read-for-all file request.
342 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
346 // If the resource is not a collection, and the resource path
347 // ends with "/" or "\", return NOT FOUND.
349 if (path.endsWith("/") || path.endsWith("\\")) {
350 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
354 // Workaround for IE's broken caching behavior.
356 resp.setHeader("Expires", "-1");
358 // A request for upload progress.
359 if (progress != null && content) {
361 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
364 serveProgress(req, resp, progress, user, file);
368 // Fetch the version to retrieve, if specified.
369 String verStr = req.getParameter(VERSION_PARAM);
371 FileBodyDTO oldBody = null;
372 if (verStr != null && file != null)
374 version = Integer.valueOf(verStr);
375 } catch (NumberFormatException e) {
376 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
381 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
382 } catch (RpcException e) {
383 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
385 } catch (ObjectNotFoundException e) {
386 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
388 } catch (InsufficientPermissionsException e) {
389 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
393 // Check if the conditions specified in the optional If headers are
394 // satisfied. Doing this for folders would require recursive checking
395 // for all of their children, which in turn would defy the purpose of
398 // Checking If headers.
399 if (!checkIfHeaders(req, resp, file, oldBody))
402 else if(!checkIfModifiedSince(req, resp, folder))
405 // Find content type.
406 String contentType = null;
407 boolean isContentHtml = false;
410 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
411 if (contentType == null) {
412 contentType = context.getMimeType(file.getName());
413 file.setMimeType(contentType);
415 } else { // folder != null
416 String accept = req.getHeader("Accept");
417 // The order in this conditional pessimizes the common API case,
418 // but is important for backwards compatibility with existing
419 // clients who send no accept header and expect a JSON response.
420 if (accept != null && accept.contains("text/html")) {
421 contentType = "text/html;charset=UTF-8";
422 isContentHtml = true;
424 contentType = "application/json;charset=UTF-8";
428 ArrayList ranges = null;
429 long contentLength = -1L;
432 // Parse range specifier.
433 ranges = parseRange(req, resp, file, oldBody);
435 resp.setHeader("ETag", getETag(file, oldBody));
436 // Last-Modified header.
437 String lastModified = oldBody == null ?
438 getLastModifiedHttp(file.getAuditInfo()) :
439 getLastModifiedHttp(oldBody.getAuditInfo());
440 resp.setHeader("Last-Modified", lastModified);
441 // X-GSS-Metadata header.
443 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
444 } catch (InsufficientPermissionsException e) {
445 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
448 // Get content length.
449 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
450 // Special case for zero length files, which would cause a
451 // (silent) ISE when setting the output buffer size.
452 if (contentLength == 0L)
455 // Set the folder X-GSS-Metadata header.
457 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
458 } catch (InsufficientPermissionsException e) {
459 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
463 ServletOutputStream ostream = null;
464 PrintWriter writer = null;
468 ostream = resp.getOutputStream();
469 } catch (IllegalStateException e) {
470 // If it fails, we try to get a Writer instead if we're
471 // trying to serve a text file
472 if ( contentType == null
473 || contentType.startsWith("text")
474 || contentType.endsWith("xml") )
475 writer = resp.getWriter();
480 || (ranges == null || ranges.isEmpty())
481 && req.getHeader("Range") == null
483 // Set the appropriate output headers
484 if (contentType != null) {
485 if (logger.isDebugEnabled())
486 logger.debug("contentType='" + contentType + "'");
487 resp.setContentType(contentType);
489 if (file != null && contentLength >= 0) {
490 if (logger.isDebugEnabled())
491 logger.debug("contentLength=" + contentLength);
492 if (contentLength < Integer.MAX_VALUE)
493 resp.setContentLength((int) contentLength);
496 // Set the content-length as String to be able to use a long
497 resp.setHeader("content-length", "" + contentLength);
500 InputStream renderResult = null;
501 String relativePath = getRelativePath(req);
502 String contextPath = req.getContextPath();
503 String servletPath = req.getServletPath();
504 String contextServletPath = contextPath + servletPath;
505 if (folder != null && content)
506 // Serve the directory browser for a public folder
508 renderResult = renderHtml(contextServletPath, relativePath, folder,user);
509 // Serve the directory for an ordinary folder
512 renderResult = renderJson(user, folder);
513 } catch (InsufficientPermissionsException e) {
514 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
519 // Copy the input stream to our output stream (if requested)
522 resp.setBufferSize(output);
523 } catch (IllegalStateException e) {
528 if (needsContentDisposition(req))
529 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
531 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
533 copy(file, renderResult, ostream, req, oldBody);
535 copy(file, renderResult, writer, req, oldBody);
536 if (file!=null) updateAccounting(owner, new Date(), contentLength);
537 } catch (ObjectNotFoundException e) {
538 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
540 } catch (InsufficientPermissionsException e) {
541 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
543 } catch (RpcException e) {
544 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
549 if (ranges == null || ranges.isEmpty())
551 // Partial content response.
552 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
554 if (ranges.size() == 1) {
555 Range range = (Range) ranges.get(0);
556 resp.addHeader("Content-Range", "bytes "
558 + "-" + range.end + "/"
560 long length = range.end - range.start + 1;
561 if (length < Integer.MAX_VALUE)
562 resp.setContentLength((int) length);
564 // Set the content-length as String to be able to use a long
565 resp.setHeader("content-length", "" + length);
567 if (contentType != null) {
568 if (logger.isDebugEnabled())
569 logger.debug("contentType='" + contentType + "'");
570 resp.setContentType(contentType);
575 resp.setBufferSize(output);
576 } catch (IllegalStateException e) {
581 copy(file, ostream, range, req, oldBody);
583 copy(file, writer, range, req, oldBody);
584 updateAccounting(owner, new Date(), contentLength);
585 } catch (ObjectNotFoundException e) {
586 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
588 } catch (InsufficientPermissionsException e) {
589 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
591 } catch (RpcException e) {
592 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
597 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
600 resp.setBufferSize(output);
601 } catch (IllegalStateException e) {
606 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
608 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
609 updateAccounting(owner, new Date(), contentLength);
610 } catch (ObjectNotFoundException e) {
611 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
613 } catch (InsufficientPermissionsException e) {
614 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
616 } catch (RpcException e) {
617 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
626 * Handles an authentication failure. If no Authorization or Date request
627 * parameters and no Authorization, Date or X-GSS-Date headers were present,
628 * this is a browser request, so redirect to login and then let the user get
629 * back to the file. Otherwise it's a bogus client request and Forbidden is
632 private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
633 if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
634 req.getParameter(DATE_PARAMETER) == null &&
635 req.getHeader(AUTHORIZATION_HEADER) == null &&
636 req.getDateHeader(DATE_HEADER) == -1 &&
637 req.getDateHeader(GSS_DATE_HEADER) == -1)
638 resp.sendRedirect(getConfiguration().getString("loginUrl") +
639 "?next=" + req.getRequestURL().toString());
641 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
645 * Return the filename of the specified file properly formatted for
646 * including in the Content-Disposition header.
648 private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
649 return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
653 * Determines whether the user agent needs the Content-Disposition
654 * header to be set, in order to properly download a file.
656 * @param req the HTTP request
657 * @return true if the Content-Disposition HTTP header must be set
659 private boolean needsContentDisposition(HttpServletRequest req) {
660 /*String agent = req.getHeader("user-agent");
661 if (agent != null && agent.contains("MSIE"))
663 String dl = req.getParameter("dl");
670 * Sends a progress update on the amount of bytes received until now for
671 * a file that the current user is currently uploading.
673 * @param req the HTTP request
674 * @param resp the HTTP response
675 * @param parameter the value for the progress request parameter
676 * @param user the current user
677 * @param file the file being uploaded, or null if the request is about a new file
678 * @throws IOException if an I/O error occurs
680 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
681 String parameter, User user, FileHeaderDTO file) throws IOException {
682 String filename = file == null ? parameter : file.getName();
684 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
685 if (status == null) {
686 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
689 JSONObject json = new JSONObject();
690 json.put("bytesUploaded", status.getBytesUploaded()).
691 put("bytesTotal", status.getFileSize());
692 sendJson(req, resp, json.toString());
694 // Workaround for IE's broken caching behavior.
695 resp.setHeader("Expires", "-1");
697 } catch (RpcException e) {
698 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
700 } catch (JSONException e) {
701 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
707 * Server a POST request to create/modify a file or folder.
709 * @param req the HTTP request
710 * @param resp the HTTP response
711 * @exception IOException if an input/output error occurs
713 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
714 boolean authDeferred = getAuthDeferred(req);
715 if (!authDeferred && req.getParameterMap().size() > 1) {
716 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
719 String path = getInnerPath(req, PATH_FILES);
720 path = path.endsWith("/")? path: path + '/';
722 path = URLDecoder.decode(path, "UTF-8");
723 } catch (IllegalArgumentException e) {
724 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
727 // We only defer authenticating multipart POST requests.
729 if (!ServletFileUpload.isMultipartContent(req)) {
730 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
733 handleMultipart(req, resp, path);
737 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
738 if (!isValidResourceName(newName)) {
739 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
742 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
743 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
744 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
745 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
746 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
747 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
750 createFolder(req, resp, path, newName);
751 else if (hasUpdateParam)
752 updateResource(req, resp, path);
753 else if (hasTrashParam)
754 trashResource(req, resp, path);
755 else if (hasRestoreParam)
756 restoreResource(req, resp, path);
757 else if (copyTo != null)
758 copyResource(req, resp, path, copyTo);
759 else if (moveTo != null)
760 moveResource(req, resp, path, moveTo);
761 else if (restoreVersion != null)
762 restoreVersion(req, resp, path, restoreVersion);
764 // IE with Gears uses POST for multiple uploads.
765 putResource(req, resp);
769 * Restores a previous version for a file.
771 * @param req the HTTP request
772 * @param resp the HTTP response
773 * @param path the resource path
774 * @param version the version number to restore
775 * @throws IOException if an I/O error occurs
777 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
778 final User user = getUser(req);
779 User owner = getOwner(req);
780 Object resource = null;
782 resource = getService().getResourceAtPath(owner.getId(), path, true);
783 } catch (ObjectNotFoundException e) {
784 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
786 } catch (RpcException e) {
787 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
790 if (resource instanceof FolderDTO) {
791 resp.sendError(HttpServletResponse.SC_CONFLICT);
796 final FileHeaderDTO file = (FileHeaderDTO) resource;
797 final int oldVersion = Integer.parseInt(version);
799 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
801 public Void call() throws Exception {
802 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
806 } catch (InsufficientPermissionsException e) {
807 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
808 } catch (ObjectNotFoundException e) {
809 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
810 } catch (RpcException e) {
811 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
812 } catch (GSSIOException e) {
813 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
814 } catch (QuotaExceededException e) {
815 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
816 } catch (NumberFormatException e) {
817 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
818 } catch (Exception e) {
819 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
824 * A method for handling multipart POST requests for uploading
825 * files from browser-based JavaScript clients.
827 * @param request the HTTP request
828 * @param response the HTTP response
829 * @param path the resource path
830 * @throws IOException in case an error occurs writing to the
833 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
834 if (logger.isDebugEnabled())
835 logger.debug("Multipart POST for resource: " + path);
837 User owner = getOwner(request);
838 boolean exists = true;
839 Object resource = null;
840 FileHeaderDTO file = null;
842 resource = getService().getResourceAtPath(owner.getId(), path, false);
843 } catch (ObjectNotFoundException e) {
845 } catch (RpcException e) {
846 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
851 if (resource instanceof FileHeaderDTO) {
852 file = (FileHeaderDTO) resource;
853 if (file.isDeleted()) {
854 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
858 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
863 String parentPath = null;
865 parentPath = getParentPath(path);
866 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
867 } catch (ObjectNotFoundException e) {
868 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
870 } catch (RpcException e) {
871 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
874 if (!(parent instanceof FolderDTO)) {
875 response.sendError(HttpServletResponse.SC_CONFLICT);
878 final FolderDTO folder = (FolderDTO) parent;
879 final String fileName = getLastElement(path);
881 FileItemIterator iter;
882 File uploadedFile = null;
884 // Create a new file upload handler.
885 ServletFileUpload upload = new ServletFileUpload();
886 StatusProgressListener progressListener = new StatusProgressListener(getService());
887 upload.setProgressListener(progressListener);
888 iter = upload.getItemIterator(request);
889 String dateParam = null;
891 while (iter.hasNext()) {
892 FileItemStream item = iter.next();
893 String name = item.getFieldName();
894 InputStream stream = item.openStream();
895 if (item.isFormField()) {
896 final String value = Streams.asString(stream);
897 if (name.equals(DATE_PARAMETER))
899 else if (name.equals(AUTHORIZATION_PARAMETER))
902 if (logger.isDebugEnabled())
903 logger.debug(name + ":" + value);
905 // Fetch the timestamp used to guard against replay attacks.
906 if (dateParam == null) {
907 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
913 timestamp = DateUtil.parseDate(dateParam).getTime();
914 } catch (DateParseException e) {
915 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
918 if (!isTimeValid(timestamp)) {
919 response.sendError(HttpServletResponse.SC_FORBIDDEN);
923 // Fetch the Authorization parameter and find the user specified in it.
925 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
928 String[] authParts = auth.split(" ");
929 if (authParts.length != 2) {
930 response.sendError(HttpServletResponse.SC_FORBIDDEN);
933 String username = authParts[0];
934 String signature = authParts[1];
937 user = getService().findUser(username);
938 } catch (RpcException e) {
939 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
943 response.sendError(HttpServletResponse.SC_FORBIDDEN);
946 request.setAttribute(USER_ATTRIBUTE, user);
948 // Remove the servlet path from the request URI.
949 String p = request.getRequestURI();
950 String servletPath = request.getContextPath() + request.getServletPath();
951 p = p.substring(servletPath.length());
952 // Validate the signature in the Authorization parameter.
953 String data = request.getMethod() + dateParam + p;
954 if (!isSignatureValid(signature, user, data)) {
955 response.sendError(HttpServletResponse.SC_FORBIDDEN);
959 progressListener.setUserId(user.getId());
960 progressListener.setFilename(fileName);
961 final String contentType = item.getContentType();
964 uploadedFile = getService().uploadFile(stream, user.getId());
965 } catch (IOException ex) {
966 throw new GSSIOException(ex, false);
968 FileHeaderDTO fileDTO = null;
969 final File upf = uploadedFile;
970 final FileHeaderDTO f = file;
973 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
975 public FileHeaderDTO call() throws Exception {
976 return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
980 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
982 public FileHeaderDTO call() throws Exception {
983 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
986 updateAccounting(owner, new Date(), fileDTO.getFileSize());
987 getService().removeFileUploadProgress(user.getId(), fileName);
990 // We can't return 204 here since GWT's onSubmitComplete won't fire.
991 response.setContentType("text/html");
992 response.getWriter().print("<pre></pre>");
993 } catch (FileUploadException e) {
994 String error = "Error while uploading file";
995 logger.error(error, e);
996 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
997 } catch (GSSIOException e) {
998 if (uploadedFile != null && uploadedFile.exists())
999 uploadedFile.delete();
1000 String error = "Error while uploading file";
1002 logger.error(error, e);
1004 logger.debug(error, e);
1005 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1006 } catch (DuplicateNameException e) {
1007 if (uploadedFile != null && uploadedFile.exists())
1008 uploadedFile.delete();
1009 String error = "The specified file name already exists in this folder";
1010 logger.error(error, e);
1011 response.sendError(HttpServletResponse.SC_CONFLICT, error);
1013 } catch (InsufficientPermissionsException e) {
1014 if (uploadedFile != null && uploadedFile.exists())
1015 uploadedFile.delete();
1016 String error = "You don't have the necessary permissions";
1017 logger.error(error, e);
1018 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1020 } catch (QuotaExceededException e) {
1021 if (uploadedFile != null && uploadedFile.exists())
1022 uploadedFile.delete();
1023 String error = "Not enough free space available";
1024 if (logger.isDebugEnabled())
1025 logger.debug(error, e);
1026 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1028 } catch (ObjectNotFoundException e) {
1029 if (uploadedFile != null && uploadedFile.exists())
1030 uploadedFile.delete();
1031 String error = "A specified object was not found";
1032 logger.error(error, e);
1033 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1034 } catch (RpcException e) {
1035 if (uploadedFile != null && uploadedFile.exists())
1036 uploadedFile.delete();
1037 String error = "An error occurred while communicating with the service";
1038 logger.error(error, e);
1039 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1040 } catch (Exception e) {
1041 if (uploadedFile != null && uploadedFile.exists())
1042 uploadedFile.delete();
1043 String error = "An internal server error occurred";
1044 logger.error(error, e);
1045 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1050 * Move the resource in the specified path to the specified destination.
1052 * @param req the HTTP request
1053 * @param resp the HTTP response
1054 * @param path the path of the resource
1055 * @param moveTo the destination of the move procedure
1056 * @throws IOException if an input/output error occurs
1058 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1059 final User user = getUser(req);
1060 User owner = getOwner(req);
1061 Object resource = null;
1063 resource = getService().getResourceAtPath(owner.getId(), path, true);
1064 } catch (ObjectNotFoundException e) {
1065 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1067 } catch (RpcException e) {
1068 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1072 String destination = null;
1073 User destOwner = null;
1074 boolean exists = true;
1076 destination = getDestinationPath(req, encodePath(moveTo));
1077 destination = URLDecoder.decode(destination, "UTF-8");
1078 destOwner = getDestinationOwner(req);
1079 getService().getResourceAtPath(destOwner.getId(), destination, true);
1080 } catch (ObjectNotFoundException e) {
1082 } catch (URISyntaxException e) {
1083 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1085 } catch (RpcException e) {
1086 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1090 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1095 final User dOwner = destOwner;
1096 final String dest = destination;
1097 if (resource instanceof FolderDTO) {
1098 final FolderDTO folder = (FolderDTO) resource;
1099 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1101 public Void call() throws Exception {
1102 getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1107 final FileHeaderDTO file = (FileHeaderDTO) resource;
1108 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1110 public Void call() throws Exception {
1111 getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1117 } catch (InsufficientPermissionsException e) {
1118 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1119 } catch (ObjectNotFoundException e) {
1120 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1121 } catch (RpcException e) {
1122 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1123 } catch (DuplicateNameException e) {
1124 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1125 } catch (GSSIOException e) {
1126 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1127 } catch (QuotaExceededException e) {
1128 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1129 } catch (Exception e) {
1130 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1135 * Copy the resource in the specified path to the specified destination.
1137 * @param req the HTTP request
1138 * @param resp the HTTP response
1139 * @param path the path of the resource
1140 * @param copyTo the destination of the copy procedure
1141 * @throws IOException if an input/output error occurs
1143 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1144 final User user = getUser(req);
1145 User owner = getOwner(req);
1146 Object resource = null;
1148 resource = getService().getResourceAtPath(owner.getId(), path, true);
1149 } catch (ObjectNotFoundException e) {
1150 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1152 } catch (RpcException e) {
1153 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1157 String destination = null;
1158 User destOwner = null;
1159 boolean exists = true;
1161 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1162 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1163 destOwner = getDestinationOwner(req);
1164 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1165 } catch (ObjectNotFoundException e) {
1167 } catch (URISyntaxException e) {
1168 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1170 } catch (RpcException e) {
1171 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1175 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1180 final User dOwner = destOwner;
1181 final String dest = destination;
1182 if (resource instanceof FolderDTO) {
1183 final FolderDTO folder = (FolderDTO) resource;
1184 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1186 public Void call() throws Exception {
1187 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1192 final FileHeaderDTO file = (FileHeaderDTO) resource;
1193 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1195 public Void call() throws Exception {
1196 getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1201 } catch (InsufficientPermissionsException e) {
1202 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1203 } catch (ObjectNotFoundException e) {
1204 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1205 } catch (RpcException e) {
1206 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1207 } catch (DuplicateNameException e) {
1208 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1209 } catch (GSSIOException e) {
1210 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1211 } catch (QuotaExceededException e) {
1212 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1213 } catch (Exception e) {
1214 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1218 private String encodePath(String path) throws UnsupportedEncodingException{
1219 StringTokenizer str = new StringTokenizer(path, "/:", true);
1220 String result = new String();
1221 while(str.hasMoreTokens()){
1222 String token = str.nextToken();
1223 if(!token.equals("/") && !token.equals(":"))
1224 token = URLEncoder.encode(token,"UTF-8");
1225 result = result + token;
1230 * A helper method that extracts the relative resource path,
1231 * after removing the 'files' namespace.
1232 * The path returned is <i>not</i> URL-decoded.
1234 * @param req the HTTP request
1235 * @param path the specified path
1236 * @return the path relative to the root folder
1237 * @throws URISyntaxException
1238 * @throws RpcException in case an error occurs while communicating
1240 * @throws UnsupportedEncodingException
1242 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1243 URI uri = new URI(path);
1244 String dest = uri.getRawPath();
1245 // Remove the context path from the destination URI.
1246 String contextPath = req.getContextPath();
1247 if (!dest.startsWith(contextPath))
1248 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1249 dest = dest.substring(contextPath.length());
1250 // Remove the servlet path from the destination URI.
1251 String servletPath = req.getServletPath();
1252 if (!dest.startsWith(servletPath))
1253 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1254 dest = dest.substring(servletPath.length());
1255 // Strip the username part
1256 if (dest.length() < 2)
1257 throw new URISyntaxException(dest, "No username in the destination URI");
1258 int slash = dest.substring(1).indexOf('/');
1260 throw new URISyntaxException(dest, "No username in the destination URI");
1261 // Decode the user to get the proper characters (mainly the @)
1262 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1264 o = getService().findUser(owner);
1266 throw new URISyntaxException(dest, "User " + owner + " not found");
1268 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1269 dest = dest.substring(slash + 1);
1271 // Chop the resource namespace part
1272 dest = dest.substring(RequestHandler.PATH_FILES.length());
1274 dest = dest.endsWith("/")? dest: dest + '/';
1279 * Move the resource in the specified path to the trash bin.
1281 * @param req the HTTP request
1282 * @param resp the HTTP response
1283 * @param path the path of the resource
1284 * @throws IOException if an input/output error occurs
1286 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1287 final User user = getUser(req);
1288 User owner = getOwner(req);
1289 Object resource = null;
1291 resource = getService().getResourceAtPath(owner.getId(), path, true);
1292 } catch (ObjectNotFoundException e) {
1293 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1295 } catch (RpcException e) {
1296 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1301 if (resource instanceof FolderDTO) {
1302 final FolderDTO folder = (FolderDTO) resource;
1303 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1305 public Void call() throws Exception {
1306 getService().moveFolderToTrash(user.getId(), folder.getId());
1311 final FileHeaderDTO file = (FileHeaderDTO) resource;
1312 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1314 public Void call() throws Exception {
1315 getService().moveFileToTrash(user.getId(), file.getId());
1320 } catch (InsufficientPermissionsException e) {
1321 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1322 } catch (ObjectNotFoundException e) {
1323 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1324 } catch (RpcException e) {
1325 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1326 } catch (Exception e) {
1327 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1332 * Restore the resource in the specified path from the trash bin.
1334 * @param req the HTTP request
1335 * @param resp the HTTP response
1336 * @param path the path of the resource
1337 * @throws IOException if an input/output error occurs
1339 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1340 final User user = getUser(req);
1341 User owner = getOwner(req);
1342 Object resource = null;
1344 resource = getService().getResourceAtPath(owner.getId(), path, false);
1345 } catch (ObjectNotFoundException e) {
1346 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1348 } catch (RpcException e) {
1349 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1354 if (resource instanceof FolderDTO) {
1355 final FolderDTO folder = (FolderDTO) resource;
1356 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1358 public Void call() throws Exception {
1359 getService().removeFolderFromTrash(user.getId(), folder.getId());
1364 final FileHeaderDTO file = (FileHeaderDTO) resource;
1365 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1367 public Void call() throws Exception {
1368 getService().removeFileFromTrash(user.getId(), file.getId());
1373 } catch (InsufficientPermissionsException e) {
1374 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1375 } catch (ObjectNotFoundException e) {
1376 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1377 } catch (RpcException e) {
1378 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1379 } catch (Exception e) {
1380 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1385 * Update the resource in the specified path.
1387 * @param req the HTTP request
1388 * @param resp the HTTP response
1389 * @param path the path of the resource
1390 * @throws IOException if an input/output error occurs
1392 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1393 final User user = getUser(req);
1394 User owner = getOwner(req);
1395 Object resource = null;
1398 resource = getService().getResourceAtPath(owner.getId(), path, false);
1399 } catch (ObjectNotFoundException e) {
1400 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1402 } catch (RpcException e) {
1403 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1406 StringBuffer input = new StringBuffer();
1407 JSONObject json = null;
1408 if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1409 input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1411 // Assume application/json
1412 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1414 while ((line = reader.readLine()) != null)
1419 json = new JSONObject(input.toString());
1420 if (logger.isDebugEnabled())
1421 logger.debug("JSON update: " + json);
1422 if (resource instanceof FolderDTO) {
1423 final FolderDTO folder = (FolderDTO) resource;
1424 String name = json.optString("name");
1425 if (!name.isEmpty())
1427 name = URLDecoder.decode(name, "UTF-8");
1428 } catch (IllegalArgumentException e) {
1429 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1432 JSONArray permissions = json.optJSONArray("permissions");
1433 Set<PermissionDTO> perms = null;
1434 if (permissions != null)
1435 perms = parsePermissions(user, permissions);
1436 Boolean readForAll = null;
1437 if (json.opt("readForAll") != null)
1438 readForAll = json.optBoolean("readForAll");
1439 if (!name.isEmpty() || permissions != null || readForAll != null) {
1440 final String fName = name.isEmpty()? null: name;
1441 final Boolean freadForAll = readForAll;
1442 final Set<PermissionDTO> fPerms = perms;
1443 FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1445 public FolderDTO call() throws Exception {
1446 return getService().updateFolder(user.getId(), folder.getId(), fName, freadForAll, fPerms);
1450 resp.getWriter().println(getNewUrl(req, folderUpdated));
1453 final FileHeaderDTO file = (FileHeaderDTO) resource;
1455 if (json.opt("name") != null)
1456 name = json.optString("name");
1457 Long modificationDate = null;
1458 if (json.optLong("modificationDate") != 0)
1459 modificationDate = json.optLong("modificationDate");
1460 Boolean versioned = null;
1461 if (json.opt("versioned") != null)
1462 versioned = json.getBoolean("versioned");
1463 JSONArray tagset = json.optJSONArray("tags");
1465 StringBuffer t = new StringBuffer();
1466 if (tagset != null) {
1467 for (int i = 0; i < tagset.length(); i++)
1468 t.append(tagset.getString(i) + ',');
1469 tags = t.toString();
1471 JSONArray permissions = json.optJSONArray("permissions");
1472 Set<PermissionDTO> perms = null;
1473 if (permissions != null)
1474 perms = parsePermissions(user, permissions);
1475 Boolean readForAll = null;
1476 if (json.opt("readForAll") != null)
1477 readForAll = json.optBoolean("readForAll");
1478 if (name != null || tags != null || modificationDate != null
1479 || versioned != null || perms != null
1480 || readForAll != null) {
1481 final String fName = name;
1482 final String fTags = tags;
1483 final Date mDate = modificationDate != null? new Date(modificationDate): null;
1484 final Boolean fVersioned = versioned;
1485 final Boolean fReadForAll = readForAll;
1486 final Set<PermissionDTO> fPerms = perms;
1487 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1489 public Object call() throws Exception {
1490 getService().updateFile(user.getId(), file.getId(),
1491 fName, fTags, mDate, fVersioned,
1492 fReadForAll, fPerms);
1499 } catch (JSONException e) {
1500 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1501 } catch (InsufficientPermissionsException e) {
1502 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1503 } catch (ObjectNotFoundException e) {
1504 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1505 } catch (DuplicateNameException e) {
1506 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1507 } catch (RpcException e) {
1508 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1509 } catch (Exception e) {
1510 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1516 * Returns the new URL of an updated folder.
1518 private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1519 String parentUrl =URLDecoder.decode(getContextPath(req, true),"UTF-8");
1520 String fpath = URLDecoder.decode(req.getPathInfo(), "UTF-8");
1521 if (parentUrl.indexOf(fpath) != -1)
1522 parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1523 if(!parentUrl.endsWith("/"))
1524 parentUrl = parentUrl+"/";
1525 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1530 * Helper method to convert a JSON array of permissions into a set of
1531 * PermissionDTO objects.
1533 * @param user the current user
1534 * @param permissions the JSON array to parse
1535 * @return the parsed set of permissions
1536 * @throws JSONException if there was an error parsing the JSON object
1537 * @throws RpcException if there was an error communicating with the EJB
1538 * @throws ObjectNotFoundException if the user could not be found
1539 * @throws UnsupportedEncodingException
1541 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1542 throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1543 if (permissions == null)
1545 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1546 for (int i = 0; i < permissions.length(); i++) {
1547 JSONObject j = permissions.getJSONObject(i);
1548 PermissionDTO perm = new PermissionDTO();
1549 perm.setModifyACL(j.optBoolean("modifyACL"));
1550 perm.setRead(j.optBoolean("read"));
1551 perm.setWrite(j.optBoolean("write"));
1552 String permUser = j.optString("user");
1553 if (!permUser.isEmpty()) {
1554 User u = getService().findUser(permUser);
1556 throw new ObjectNotFoundException("User " + permUser + " not found");
1557 perm.setUser(u.getDTO());
1559 // 31/8/2009: Add optional groupUri which takes priority if it exists
1560 String permGroupUri = j.optString("groupUri");
1561 String permGroup = j.optString("group");
1562 if (!permGroupUri.isEmpty()) {
1563 String[] names = permGroupUri.split("/");
1564 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1565 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1566 User u = getService().findUser(usr);
1568 throw new ObjectNotFoundException("User " + permUser + " not found");
1569 GroupDTO g = getService().getGroup(u.getId(), grp);
1572 else if (!permGroup.isEmpty()) {
1573 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1576 if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1577 throw new JSONException("A permission must correspond to either a user or a group");
1584 * Creates a new folder with the specified name under the folder in the provided path.
1586 * @param req the HTTP request
1587 * @param resp the HTTP response
1588 * @param path the parent folder path
1589 * @param folderName the name of the new folder
1590 * @throws IOException if an input/output error occurs
1592 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1593 if (logger.isDebugEnabled())
1594 logger.debug("Creating folder " + folderName + " in '" + path);
1596 final User user = getUser(req);
1597 User owner = getOwner(req);
1598 boolean exists = true;
1600 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1601 } catch (ObjectNotFoundException e) {
1603 } catch (RpcException e) {
1604 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1609 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1610 ", " + METHOD_HEAD);
1611 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1617 parent = getService().getResourceAtPath(owner.getId(), path, true);
1618 } catch (ObjectNotFoundException e) {
1619 resp.sendError(HttpServletResponse.SC_CONFLICT);
1621 } catch (RpcException e) {
1622 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1626 if (parent instanceof FolderDTO) {
1627 final FolderDTO folder = (FolderDTO) parent;
1628 FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1630 public FolderDTO call() throws Exception {
1631 return getService().createFolder(user.getId(), folder.getId(), folderName);
1635 String newResource = getApiRoot() + newFolder.getURI();
1636 resp.setHeader("Location", newResource);
1637 resp.setContentType("text/plain");
1638 PrintWriter out = resp.getWriter();
1639 out.println(newResource);
1641 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1644 } catch (DuplicateNameException e) {
1645 resp.sendError(HttpServletResponse.SC_CONFLICT);
1647 } catch (InsufficientPermissionsException e) {
1648 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1650 } catch (ObjectNotFoundException e) {
1651 resp.sendError(HttpServletResponse.SC_CONFLICT);
1653 } catch (RpcException e) {
1654 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1656 } catch (Exception e) {
1657 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1660 resp.setStatus(HttpServletResponse.SC_CREATED);
1666 * @throws IOException
1667 * @throws FileNotFoundException
1669 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1670 String path = getInnerPath(req, PATH_FILES);
1672 path = URLDecoder.decode(path, "UTF-8");
1673 } catch (IllegalArgumentException e) {
1674 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1677 if (logger.isDebugEnabled())
1678 logger.debug("Updating resource: " + path);
1680 final User user = getUser(req);
1681 User owner = getOwner(req);
1682 boolean exists = true;
1683 Object resource = null;
1684 FileHeaderDTO file = null;
1686 resource = getService().getResourceAtPath(owner.getId(), path, false);
1687 } catch (ObjectNotFoundException e) {
1689 } catch (RpcException e) {
1690 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1695 if (resource instanceof FileHeaderDTO)
1696 file = (FileHeaderDTO) resource;
1698 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1701 boolean result = true;
1703 // Temporary content file used to support partial PUT.
1704 File contentFile = null;
1706 Range range = parseContentRange(req, resp);
1708 InputStream resourceInputStream = null;
1710 // Append data specified in ranges to existing content for this
1711 // resource - create a temporary file on the local filesystem to
1712 // perform this operation.
1713 // Assume just one range is specified for now
1714 if (range != null) {
1716 contentFile = executePartialPut(req, range, path);
1717 } catch (RpcException e) {
1718 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1720 } catch (ObjectNotFoundException e) {
1721 resp.sendError(HttpServletResponse.SC_CONFLICT);
1723 } catch (InsufficientPermissionsException e) {
1724 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1727 resourceInputStream = new FileInputStream(contentFile);
1729 resourceInputStream = req.getInputStream();
1732 FolderDTO folder = null;
1733 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1734 if (!(parent instanceof FolderDTO)) {
1735 resp.sendError(HttpServletResponse.SC_CONFLICT);
1738 folder = (FolderDTO) parent;
1739 final String name = getLastElement(path);
1740 final String mimeType = context.getMimeType(name);
1741 File uploadedFile = null;
1743 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1744 } catch (IOException ex) {
1745 throw new GSSIOException(ex, false);
1747 FileHeaderDTO fileDTO = null;
1748 final File uploadedf = uploadedFile;
1749 final FolderDTO parentf = folder;
1750 final FileHeaderDTO f = file;
1752 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1754 public FileHeaderDTO call() throws Exception {
1755 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1759 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1761 public FileHeaderDTO call() throws Exception {
1762 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1766 updateAccounting(owner, new Date(), fileDTO.getFileSize());
1767 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1768 } catch(ObjectNotFoundException e) {
1770 } catch (RpcException e) {
1771 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1773 } catch (IOException e) {
1774 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1776 } catch (GSSIOException e) {
1777 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1779 } catch (DuplicateNameException e) {
1780 resp.sendError(HttpServletResponse.SC_CONFLICT);
1782 } catch (InsufficientPermissionsException e) {
1783 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1785 } catch (QuotaExceededException e) {
1786 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1788 } catch (Exception e) {
1789 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1795 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1797 resp.setStatus(HttpServletResponse.SC_CREATED);
1799 resp.sendError(HttpServletResponse.SC_CONFLICT);
1803 * Delete a resource.
1805 * @param req The servlet request we are processing
1806 * @param resp The servlet response we are processing
1807 * @throws IOException if the response cannot be sent
1809 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1810 String path = getInnerPath(req, PATH_FILES);
1811 if (logger.isDebugEnabled())
1812 logger.debug("Deleting resource '" + path);
1813 path = URLDecoder.decode(path, "UTF-8");
1814 final User user = getUser(req);
1815 User owner = getOwner(req);
1816 boolean exists = true;
1817 Object object = null;
1819 object = getService().getResourceAtPath(owner.getId(), path, false);
1820 } catch (ObjectNotFoundException e) {
1822 } catch (RpcException e) {
1823 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1828 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1832 FolderDTO folder = null;
1833 FileHeaderDTO file = null;
1834 if (object instanceof FolderDTO)
1835 folder = (FolderDTO) object;
1837 file = (FileHeaderDTO) object;
1841 final FileHeaderDTO f = file;
1842 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1844 public Void call() throws Exception {
1845 getService().deleteFile(user.getId(), f.getId());
1849 } catch (InsufficientPermissionsException e) {
1850 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1852 } catch (ObjectNotFoundException e) {
1853 // Although we had already found the object, it was
1854 // probably deleted from another thread.
1855 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1857 } catch (RpcException e) {
1858 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1860 } catch (Exception e) {
1861 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1864 else if (folder != null)
1866 final FolderDTO fo = folder;
1867 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1869 public Void call() throws Exception {
1870 getService().deleteFolder(user.getId(), fo.getId());
1874 } catch (InsufficientPermissionsException e) {
1875 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1877 } catch (ObjectNotFoundException e) {
1878 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1880 } catch (RpcException e) {
1881 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1883 } catch (Exception e) {
1884 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1887 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1892 * Return an InputStream to a JSON representation of the contents
1893 * of this directory.
1895 * @param user the user that made the request
1896 * @param folder the specified directory
1897 * @return an input stream with the rendered contents
1898 * @throws IOException if the response cannot be sent
1899 * @throws ServletException
1900 * @throws InsufficientPermissionsException if the user does not have
1901 * the necessary privileges to read the directory
1903 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1904 ServletException, InsufficientPermissionsException {
1905 JSONObject json = new JSONObject();
1907 json.put("name", folder.getName()).
1908 put("owner", folder.getOwner().getUsername()).
1909 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1910 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1911 put("deleted", folder.isDeleted()).
1912 put("readForAll", folder.isReadForAll());
1914 if (folder.getAuditInfo().getModifiedBy() != null)
1915 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1916 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1917 if (folder.getParent() != null) {
1918 JSONObject j = new JSONObject();
1919 j.put("uri", getApiRoot() + folder.getParent().getURI());
1920 j.put("name", folder.getParent().getName());
1921 json.put("parent", j);
1923 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1924 for (FolderDTO f: folder.getSubfolders())
1925 if (!f.isDeleted()) {
1926 JSONObject j = new JSONObject();
1927 j.put("name", f.getName()).
1928 put("uri", getApiRoot() + f.getURI());
1931 json.put("folders", subfolders);
1932 List<JSONObject> files = new ArrayList<JSONObject>();
1933 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1934 for (FileHeaderDTO f: fileHeaders) {
1935 JSONObject j = new JSONObject();
1936 j.put("name", f.getName()).
1937 put("owner", f.getOwner().getUsername()).
1938 put("deleted", f.isDeleted()).
1939 put("version", f.getVersion()).
1940 put("content", f.getMimeType()).
1941 put("size", f.getFileSize()).
1942 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1943 put("path", f.getFolder().getPath()).
1944 put("uri", getApiRoot() + f.getURI());
1945 if (f.getAuditInfo().getModificationDate() != null)
1946 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1949 json.put("files", files);
1950 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1951 json.put("permissions", renderJson(perms));
1952 } catch (JSONException e) {
1953 throw new ServletException(e);
1954 } catch (ObjectNotFoundException e) {
1955 throw new ServletException(e);
1956 } catch (RpcException e) {
1957 throw new ServletException(e);
1960 // Prepare a writer to a buffered area
1961 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1962 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1963 PrintWriter writer = new PrintWriter(osWriter);
1965 // Return an input stream to the underlying bytes
1966 writer.write(json.toString());
1968 return new ByteArrayInputStream(stream.toByteArray());
1972 * Return a String with a JSON representation of the metadata
1973 * of the specified folder.
1974 * @throws RpcException
1975 * @throws InsufficientPermissionsException
1976 * @throws ObjectNotFoundException
1978 private String renderJsonMetadata(User user, FolderDTO folder)
1979 throws ServletException, InsufficientPermissionsException {
1980 // Check if the user has read permission.
1982 if (!getService().canReadFolder(user.getId(), folder.getId()))
1983 throw new InsufficientPermissionsException();
1984 } catch (ObjectNotFoundException e) {
1985 throw new ServletException(e);
1986 } catch (RpcException e) {
1987 throw new ServletException(e);
1990 JSONObject json = new JSONObject();
1992 json.put("name", folder.getName()).
1993 put("owner", folder.getOwner().getUsername()).
1994 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1995 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1996 put("deleted", folder.isDeleted());
1997 if (folder.getAuditInfo().getModifiedBy() != null)
1998 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1999 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
2000 } catch (JSONException e) {
2001 throw new ServletException(e);
2003 return json.toString();
2007 * Return a String with a JSON representation of the metadata
2008 * of the specified file. If an old file body is provided, then
2009 * the metadata of that particular version will be returned.
2011 * @param user the user that made the request
2012 * @param file the specified file header
2013 * @param oldBody the version number
2014 * @return the JSON-encoded file
2015 * @throws ServletException
2016 * @throws InsufficientPermissionsException if the user does not have
2017 * the necessary privileges to read the directory
2019 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
2020 throws ServletException, InsufficientPermissionsException {
2021 JSONObject json = new JSONObject();
2023 // Need to encode file name in order to properly display it in the web client.
2024 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2025 put("owner", file.getOwner().getUsername()).
2026 put("versioned", file.isVersioned()).
2027 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
2028 put("readForAll", file.isReadForAll()).
2029 put("tags", renderJson(file.getTags())).
2030 put("path", file.getFolder().getPath()).
2031 put("uri", getApiRoot() + file.getURI()).
2032 put("deleted", file.isDeleted());
2033 JSONObject j = new JSONObject();
2034 j.put("uri", getApiRoot() + file.getFolder().getURI()).
2035 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2036 json.put("folder", j);
2037 if (oldBody != null)
2038 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2039 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2040 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2041 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2042 put("content", oldBody.getMimeType()).
2043 put("size", oldBody.getFileSize());
2045 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2046 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2047 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2048 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2049 put("content", file.getMimeType()).
2050 put("size", file.getFileSize());
2051 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
2052 json.put("permissions", renderJson(perms));
2053 } catch (JSONException e) {
2054 throw new ServletException(e);
2055 } catch (ObjectNotFoundException e) {
2056 throw new ServletException(e);
2057 } catch (RpcException e) {
2058 throw new ServletException(e);
2059 } catch (UnsupportedEncodingException e) {
2060 throw new ServletException(e);
2063 return json.toString();
2067 * Return a String with a JSON representation of the
2068 * specified set of permissions.
2070 * @param permissions the set of permissions
2071 * @return the JSON-encoded object
2072 * @throws JSONException
2073 * @throws UnsupportedEncodingException
2075 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2076 JSONArray perms = new JSONArray();
2077 for (PermissionDTO p: permissions) {
2078 JSONObject permission = new JSONObject();
2079 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2080 if (p.getUser() != null)
2081 permission.put("user", p.getUser().getUsername());
2082 if (p.getGroup() != null) {
2083 GroupDTO group = p.getGroup();
2084 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2085 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2087 perms.put(permission);
2093 * Return a String with a JSON representation of the
2094 * specified collection of tags.
2096 * @param tags the collection of tags
2097 * @return the JSON-encoded object
2098 * @throws JSONException
2099 * @throws UnsupportedEncodingException
2101 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2102 JSONArray tagArray = new JSONArray();
2103 for (String t: tags)
2104 tagArray.put(URLEncoder.encode(t,"UTF-8"));
2109 * Retrieves the user who owns the destination namespace, for a
2110 * copy or move request.
2112 * @param req the HTTP request
2113 * @return the owner of the namespace
2115 protected User getDestinationOwner(HttpServletRequest req) {
2116 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2120 * A helper inner class for updating the progress status of a file upload.
2124 public static class StatusProgressListener implements ProgressListener {
2125 private int percentLogged = 0;
2126 private long bytesTransferred = 0;
2128 private long fileSize = -100;
2130 private Long userId;
2132 private String filename;
2134 private ExternalAPI service;
2136 public StatusProgressListener(ExternalAPI aService) {
2141 * Modify the userId.
2143 * @param aUserId the userId to set
2145 public void setUserId(Long aUserId) {
2150 * Modify the filename.
2152 * @param aFilename the filename to set
2154 public void setFilename(String aFilename) {
2155 filename = aFilename;
2159 public void update(long bytesRead, long contentLength, int items) {
2160 //monitoring per percent of bytes uploaded
2161 bytesTransferred = bytesRead;
2162 if (fileSize != contentLength)
2163 fileSize = contentLength;
2164 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2166 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2167 if (percent != percentLogged){
2168 percentLogged = percent;
2170 if (userId != null && filename != null)
2171 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2172 } catch (ObjectNotFoundException e) {
2173 // Swallow the exception since it is going to be caught
2174 // by previously called methods
2180 * Return an InputStream to an HTML representation of the contents of this
2183 * @param contextPath Context path to which our internal paths are relative
2184 * @param relativePath the requested relative path to the resource
2185 * @param folder the specified directory
2186 * @param req the HTTP request
2187 * @return an input stream with the rendered contents
2188 * @throws IOException
2189 * @throws ServletException
2191 private InputStream renderHtml(String contextPath, String relativePath, FolderDTO folder, User user) throws IOException, ServletException {
2192 String name = folder.getName();
2193 // Prepare a writer to a buffered area
2194 ByteArrayOutputStream stream = new ByteArrayOutputStream();
2195 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2196 PrintWriter writer = new PrintWriter(osWriter);
2197 StringBuffer sb = new StringBuffer();
2198 // rewriteUrl(contextPath) is expensive. cache result for later reuse
2199 String rewrittenContextPath = rewriteUrl(contextPath);
2200 // Render the page header
2201 sb.append("<html>\r\n");
2202 sb.append("<head>\r\n");
2203 sb.append("<title>");
2204 sb.append("Index of " + name);
2205 sb.append("</title>\r\n");
2206 sb.append("<STYLE><!--");
2208 sb.append("--></STYLE> ");
2209 sb.append("</head>\r\n");
2210 sb.append("<body>");
2212 sb.append("Index of " + name);
2214 // Render the link to our parent (if required)
2215 String folderPath = folder.getPath();
2216 int indexFolderPath = relativePath.indexOf(folderPath);
2217 String relativePathNoFolderName = relativePath.substring(0, indexFolderPath);
2218 String parentDirectory = folderPath;
2219 //String rewrittenParentDirectory = rewriteUrl(parentDirectory);
2220 if (parentDirectory.endsWith("/"))
2221 parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2222 int slash = parentDirectory.lastIndexOf('/');
2224 String parent = folderPath.substring(0, slash);
2225 sb.append(" - <a href=\"");
2226 sb.append(rewrittenContextPath);
2227 sb.append(relativePathNoFolderName);
2228 //sb.append(folderPath);
2229 //if (parent.equals(""))
2232 //if (!parent.endsWith("/"))
2236 sb.append("Up To " + parentDirectory);
2242 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2244 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
2246 // Render the column headings
2247 sb.append("<tr>\r\n");
2248 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2250 sb.append("</strong></font></td>\r\n");
2251 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2253 sb.append("</strong></font></td>\r\n");
2254 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2255 sb.append("Last modified");
2256 sb.append("</strong></font></td>\r\n");
2258 // Render the directory entries within this directory
2259 boolean shade = false;
2260 Iterator iter = folder.getSubfolders().iterator();
2261 while (iter.hasNext()) {
2262 FolderDTO subf = (FolderDTO) iter.next();
2263 String resourceName = subf.getName();
2264 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2269 sb.append(" bgcolor=\"#eeeeee\"");
2273 sb.append("<td align=\"left\"> \r\n");
2274 sb.append("<a href=\"");
2275 sb.append(rewrittenContextPath);
2276 sb.append(relativePathNoFolderName);
2277 sb.append(folderPath + resourceName);
2279 sb.append("\"><tt>");
2280 sb.append(RequestUtil.filter(resourceName));
2282 sb.append("</tt></a></td>\r\n");
2284 sb.append("<td align=\"right\"><tt>");
2285 sb.append(" ");
2286 sb.append("</tt></td>\r\n");
2288 sb.append("<td align=\"right\"><tt>");
2289 sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2290 sb.append("</tt></td>\r\n");
2292 sb.append("</tr>\r\n");
2294 List<FileHeaderDTO> files;
2296 files = getService().getFiles(user.getId(), folder.getId(), true);
2297 } catch (ObjectNotFoundException e) {
2298 throw new ServletException(e.getMessage());
2299 } catch (InsufficientPermissionsException e) {
2300 throw new ServletException(e.getMessage());
2301 } catch (RpcException e) {
2302 throw new ServletException(e.getMessage());
2304 for (FileHeaderDTO file : files) {
2305 String resourceName = file.getName();
2306 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2311 sb.append(" bgcolor=\"#eeeeee\"");
2315 sb.append("<td align=\"left\"> \r\n");
2316 sb.append("<a href=\"");
2317 sb.append(rewrittenContextPath);
2318 sb.append(relativePath);
2319 if(!relativePath.endsWith("/"))
2321 sb.append(rewriteUrl(resourceName));
2322 sb.append("\"><tt>");
2323 sb.append(RequestUtil.filter(resourceName));
2324 sb.append("</tt></a></td>\r\n");
2326 sb.append("<td align=\"right\"><tt>");
2327 sb.append(renderSize(file.getFileSize()));
2328 sb.append("</tt></td>\r\n");
2330 sb.append("<td align=\"right\"><tt>");
2331 sb.append(getLastModifiedHttp(file.getAuditInfo()));
2332 sb.append("</tt></td>\r\n");
2334 sb.append("</tr>\r\n");
2337 // Render the page footer
2338 sb.append("</table>\r\n");
2340 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2342 //sb.append("<h3>").append(getServletContext().getServerInfo()).append("</h3>");
2343 sb.append("</body>\r\n");
2344 sb.append("</html>\r\n");
2346 // Return an input stream to the underlying bytes
2347 writer.write(sb.toString());
2349 return new ByteArrayInputStream(stream.toByteArray());