2 * Copyright 2008, 2009, 2010 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.FileBody;
30 import gr.ebs.gss.server.domain.FileHeader;
31 import gr.ebs.gss.server.domain.FileUploadStatus;
32 import gr.ebs.gss.server.domain.Folder;
33 import gr.ebs.gss.server.domain.Group;
34 import gr.ebs.gss.server.domain.Permission;
35 import gr.ebs.gss.server.domain.User;
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 boolean exists = true;
207 Object resource = null;
208 FileHeader file = null;
209 Folder folder = null;
211 resource = getService().getResourceAtPath(owner.getId(), path, false);
212 } catch (ObjectNotFoundException e) {
214 } catch (RpcException e) {
215 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
219 if (!exists && authDeferred) {
220 // We do not want to leak information if the request
221 // was not authenticated.
222 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
226 if (resource instanceof Folder)
227 folder = (Folder) resource;
229 file = (FileHeader) resource; // Note that file will be null, if (!exists).
231 // Now it's time to perform the deferred authentication check.
232 // Since regular signature checking was already performed,
233 // we need to check the read-all flag or the signature-in-parameters.
235 if (file != null && !file.isReadForAll() && content) {
236 // Check for GET with the signature in the request parameters.
237 String auth = req.getParameter(AUTHORIZATION_PARAMETER);
238 String dateParam = req.getParameter(DATE_PARAMETER);
239 if (auth == null || dateParam == null) {
240 // Check for a valid authentication cookie.
241 if (req.getCookies() != null) {
242 boolean found = false;
243 for (Cookie cookie : req.getCookies())
244 if (Login.AUTH_COOKIE.equals(cookie.getName())) {
245 String cookieauth = cookie.getValue();
246 int sepIndex = cookieauth.indexOf(Login.COOKIE_SEPARATOR);
247 if (sepIndex == -1) {
248 handleAuthFailure(req, resp);
251 String username = URLDecoder.decode(cookieauth.substring(0, sepIndex), "US-ASCII");
254 user = getService().findUser(username);
255 } catch (RpcException e) {
256 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
260 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
263 req.setAttribute(USER_ATTRIBUTE, user);
264 String token = cookieauth.substring(sepIndex + 1);
265 if (user.getAuthToken() == null) {
266 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
269 if (!Arrays.equals(user.getAuthToken(), Base64.decodeBase64(token))) {
270 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
277 handleAuthFailure(req, resp);
281 handleAuthFailure(req, resp);
287 timestamp = DateUtil.parseDate(dateParam).getTime();
288 } catch (DateParseException e) {
289 resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
292 if (!isTimeValid(timestamp)) {
293 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
297 // Fetch the Authorization parameter and find the user specified in it.
298 String[] authParts = auth.split(" ");
299 if (authParts.length != 2) {
300 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
303 String username = authParts[0];
304 String signature = authParts[1];
307 user = getService().findUser(username);
308 } catch (RpcException e) {
309 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
313 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
316 req.setAttribute(USER_ATTRIBUTE, user);
318 // Remove the servlet path from the request URI.
319 String p = req.getRequestURI();
320 String servletPath = req.getContextPath() + req.getServletPath();
321 p = p.substring(servletPath.length());
322 // Validate the signature in the Authorization parameter.
323 String data = req.getMethod() + dateParam + p;
324 if (!isSignatureValid(signature, user, data)) {
325 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
330 else if(folder != null && folder.isReadForAll() || file != null && file.isReadForAll()){
331 //This case refers to a folder or file with public privileges
332 //For a read-for-all folder request, pretend the owner is making it.
334 req.setAttribute(USER_ATTRIBUTE, user);
335 }else if(folder != null && !folder.isReadForAll()){
336 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
340 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
344 // If the resource is not a collection, and the resource path
345 // ends with "/" or "\", return NOT FOUND.
347 if (path.endsWith("/") || path.endsWith("\\")) {
348 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
352 // Workaround for IE's broken caching behavior.
354 resp.setHeader("Expires", "-1");
356 // A request for upload progress.
357 if (progress != null && content) {
358 serveProgress(req, resp, progress, user, file);
362 // Fetch the version to retrieve, if specified.
363 String verStr = req.getParameter(VERSION_PARAM);
365 FileBody oldBody = null;
366 if (verStr != null && file != null)
368 version = Integer.valueOf(verStr);
369 } catch (NumberFormatException e) {
370 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
375 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
376 } catch (RpcException e) {
377 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
379 } catch (ObjectNotFoundException e) {
380 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
382 } catch (InsufficientPermissionsException e) {
383 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
387 // Check if the conditions specified in the optional If headers are
388 // satisfied. Doing this for folders would require recursive checking
389 // for all of their children, which in turn would defy the purpose of
392 // Checking If headers.
393 if (!checkIfHeaders(req, resp, file, oldBody))
396 else if(!checkIfModifiedSince(req, resp, folder))
399 // Find content type.
400 String contentType = null;
401 boolean isContentHtml = false;
402 boolean expectJSON = false;
405 contentType = version>0 ? oldBody.getMimeType() : file.getCurrentBody().getMimeType();
406 if (contentType == null) {
407 contentType = context.getMimeType(file.getName());
408 file.getCurrentBody().setMimeType(contentType);
410 } else { // folder != null
411 String accept = req.getHeader("Accept");
412 // The order in this conditional pessimizes the common API case,
413 // but is important for backwards compatibility with existing
414 // clients who send no accept header and expect a JSON response.
415 if (accept != null && accept.contains("text/html")) {
416 contentType = "text/html;charset=UTF-8";
417 isContentHtml = true;
418 //this is the case when clients send the appropriate headers, the contentType is "text/html"
419 //and expect a JSON response. The above check applies to FireGSS client
420 expectJSON = !authDeferred ? true : false;
423 contentType = "application/json;charset=UTF-8";
429 ArrayList ranges = null;
430 long contentLength = -1L;
433 // Parse range specifier.
434 ranges = parseRange(req, resp, file, oldBody);
436 resp.setHeader("ETag", getETag(file, oldBody));
437 // Last-Modified header.
438 String lastModified = oldBody == null ?
439 getLastModifiedHttp(file.getAuditInfo()) :
440 getLastModifiedHttp(oldBody.getAuditInfo());
441 resp.setHeader("Last-Modified", lastModified);
442 // X-GSS-Metadata header.
444 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
445 } catch (InsufficientPermissionsException e) {
446 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
449 // Get content length.
450 contentLength = version>0 ? oldBody.getFileSize() : file.getCurrentBody().getFileSize();
451 // Special case for zero length files, which would cause a
452 // (silent) ISE when setting the output buffer size.
453 if (contentLength == 0L)
456 // Set the folder X-GSS-Metadata header.
458 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
459 } catch (InsufficientPermissionsException e) {
460 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
464 ServletOutputStream ostream = null;
465 PrintWriter writer = null;
469 ostream = resp.getOutputStream();
470 } catch (IllegalStateException e) {
471 // If it fails, we try to get a Writer instead if we're
472 // trying to serve a text file
473 if ( contentType == null
474 || contentType.startsWith("text")
475 || contentType.endsWith("xml") )
476 writer = resp.getWriter();
480 if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
481 // Set the appropriate output headers
482 if (contentType != null) {
483 if (logger.isDebugEnabled())
484 logger.debug("contentType='" + contentType + "'");
485 resp.setContentType(contentType);
487 if (file != null && contentLength >= 0) {
488 if (logger.isDebugEnabled())
489 logger.debug("contentLength=" + contentLength);
490 if (contentLength < Integer.MAX_VALUE)
491 resp.setContentLength((int) contentLength);
494 // Set the content-length as String to be able to use a long
495 resp.setHeader("content-length", "" + contentLength);
498 InputStream renderResult = null;
499 String relativePath = getRelativePath(req);
500 String contextPath = req.getContextPath();
501 String servletPath = req.getServletPath();
502 String contextServletPath = contextPath + servletPath;
503 if (folder != null && content)
504 // Serve the directory browser for a public folder
505 if (isContentHtml && !expectJSON)
506 renderResult = renderHtml(contextServletPath, relativePath, folder,user);
507 // Serve the directory for an ordinary folder or for fireGSS client
510 renderResult = renderJson(user, folder);
511 } catch (InsufficientPermissionsException e) {
512 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
517 // Copy the input stream to our output stream (if requested)
520 resp.setBufferSize(output);
521 } catch (IllegalStateException e) {
526 if (needsContentDisposition(req))
527 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
529 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
531 copy(file, renderResult, ostream, req, oldBody);
533 copy(file, renderResult, writer, req, oldBody);
534 if (file!=null) updateAccounting(owner, new Date(), contentLength);
535 } catch (ObjectNotFoundException e) {
536 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
538 } catch (InsufficientPermissionsException e) {
539 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
541 } catch (RpcException e) {
542 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
547 if (ranges == null || ranges.isEmpty())
549 // Partial content response.
550 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
552 if (ranges.size() == 1) {
553 Range range = (Range) ranges.get(0);
554 resp.addHeader("Content-Range", "bytes "
556 + "-" + range.end + "/"
558 long length = range.end - range.start + 1;
559 if (length < Integer.MAX_VALUE)
560 resp.setContentLength((int) length);
562 // Set the content-length as String to be able to use a long
563 resp.setHeader("content-length", "" + length);
565 if (contentType != null) {
566 if (logger.isDebugEnabled())
567 logger.debug("contentType='" + contentType + "'");
568 resp.setContentType(contentType);
573 resp.setBufferSize(output);
574 } catch (IllegalStateException e) {
579 copy(file, ostream, range, req, oldBody);
581 copy(file, writer, range, req, oldBody);
582 updateAccounting(owner, new Date(), contentLength);
583 } catch (ObjectNotFoundException e) {
584 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
586 } catch (InsufficientPermissionsException e) {
587 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
589 } catch (RpcException e) {
590 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
595 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
598 resp.setBufferSize(output);
599 } catch (IllegalStateException e) {
604 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
606 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
607 updateAccounting(owner, new Date(), contentLength);
608 } catch (ObjectNotFoundException e) {
609 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
611 } catch (InsufficientPermissionsException e) {
612 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
614 } catch (RpcException e) {
615 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
624 * Handles an authentication failure. If no Authorization or Date request
625 * parameters and no Authorization, Date or X-GSS-Date headers were present,
626 * this is a browser request, so redirect to login and then let the user get
627 * back to the file. Otherwise it's a bogus client request and Forbidden is
630 private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
631 if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
632 req.getParameter(DATE_PARAMETER) == null &&
633 req.getHeader(AUTHORIZATION_HEADER) == null &&
634 req.getDateHeader(DATE_HEADER) == -1 &&
635 req.getDateHeader(GSS_DATE_HEADER) == -1)
636 resp.sendRedirect(getConfiguration().getString("loginUrl") +
637 "?next=" + req.getRequestURL().toString());
639 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
643 * Return the filename of the specified file properly formatted for
644 * including in the Content-Disposition header.
646 private String getDispositionFilename(FileHeader file) throws UnsupportedEncodingException {
647 return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
651 * Determines whether the user agent needs the Content-Disposition
652 * header to be set, in order to properly download a file.
654 * @param req the HTTP request
655 * @return true if the Content-Disposition HTTP header must be set
657 private boolean needsContentDisposition(HttpServletRequest req) {
658 /*String agent = req.getHeader("user-agent");
659 if (agent != null && agent.contains("MSIE"))
661 String dl = req.getParameter("dl");
668 * Sends a progress update on the amount of bytes received until now for
669 * a file that the current user is currently uploading.
671 * @param req the HTTP request
672 * @param resp the HTTP response
673 * @param parameter the value for the progress request parameter
674 * @param user the current user
675 * @param file the file being uploaded, or null if the request is about a new file
676 * @throws IOException if an I/O error occurs
678 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
679 String parameter, User user, FileHeader file) throws IOException {
680 String filename = file == null ? parameter : file.getName();
682 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
683 if (status == null) {
684 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
687 JSONObject json = new JSONObject();
688 json.put("bytesUploaded", status.getBytesUploaded()).
689 put("bytesTotal", status.getFileSize());
690 sendJson(req, resp, json.toString());
692 // Workaround for IE's broken caching behavior.
693 resp.setHeader("Expires", "-1");
695 } catch (RpcException e) {
696 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
698 } catch (JSONException e) {
699 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
705 * Server a POST request to create/modify a file or folder.
707 * @param req the HTTP request
708 * @param resp the HTTP response
709 * @exception IOException if an input/output error occurs
711 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
712 boolean authDeferred = getAuthDeferred(req);
713 if (!authDeferred && req.getParameterMap().size() > 1) {
714 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
717 String path = getInnerPath(req, PATH_FILES);
718 path = path.endsWith("/")? path: path + '/';
720 path = URLDecoder.decode(path, "UTF-8");
721 } catch (IllegalArgumentException e) {
722 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
725 // We only defer authenticating multipart POST requests.
727 if (!ServletFileUpload.isMultipartContent(req)) {
728 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
731 handleMultipart(req, resp, path);
735 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
737 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
738 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
739 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
740 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
741 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
742 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
744 if (newName != null){
745 if (!isValidResourceName(newName)) {
746 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
749 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 Folder) {
791 resp.sendError(HttpServletResponse.SC_CONFLICT);
796 final FileHeader file = (FileHeader) 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 FileHeader 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 FileHeader) {
852 file = (FileHeader) 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 Folder)) {
875 response.sendError(HttpServletResponse.SC_CONFLICT);
878 final Folder folderLocal = (Folder) parent;
879 final String fileName = getLastElement(path);
881 if (!isValidResourceName(fileName)) {
882 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
886 FileItemIterator iter;
887 File uploadedFile = null;
889 // Create a new file upload handler.
890 ServletFileUpload upload = new ServletFileUpload();
891 StatusProgressListener progressListener = new StatusProgressListener(getService());
892 upload.setProgressListener(progressListener);
893 iter = upload.getItemIterator(request);
894 String dateParam = null;
896 while (iter.hasNext()) {
897 FileItemStream item = iter.next();
898 String name = item.getFieldName();
899 InputStream stream = item.openStream();
900 if (item.isFormField()) {
901 final String value = Streams.asString(stream);
902 if (name.equals(DATE_PARAMETER))
904 else if (name.equals(AUTHORIZATION_PARAMETER))
907 if (logger.isDebugEnabled())
908 logger.debug(name + ":" + value);
910 // Fetch the timestamp used to guard against replay attacks.
911 if (dateParam == null) {
912 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
918 timestamp = DateUtil.parseDate(dateParam).getTime();
919 } catch (DateParseException e) {
920 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
923 if (!isTimeValid(timestamp)) {
924 response.sendError(HttpServletResponse.SC_FORBIDDEN);
928 // Fetch the Authorization parameter and find the user specified in it.
930 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
933 String[] authParts = auth.split(" ");
934 if (authParts.length != 2) {
935 response.sendError(HttpServletResponse.SC_FORBIDDEN);
938 String username = authParts[0];
939 String signature = authParts[1];
942 user = getService().findUser(username);
943 } catch (RpcException e) {
944 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
948 response.sendError(HttpServletResponse.SC_FORBIDDEN);
951 request.setAttribute(USER_ATTRIBUTE, user);
953 // Remove the servlet path from the request URI.
954 String p = request.getRequestURI();
955 String servletPath = request.getContextPath() + request.getServletPath();
956 p = p.substring(servletPath.length());
957 // Validate the signature in the Authorization parameter.
958 String data = request.getMethod() + dateParam + p;
959 if (!isSignatureValid(signature, user, data)) {
960 response.sendError(HttpServletResponse.SC_FORBIDDEN);
964 progressListener.setUserId(user.getId());
965 progressListener.setFilename(fileName);
966 final String contentType = item.getContentType();
969 uploadedFile = getService().uploadFile(stream, user.getId());
970 } catch (IOException ex) {
971 throw new GSSIOException(ex, false);
973 FileHeader fileLocal = null;
974 final File upf = uploadedFile;
975 final FileHeader f = file;
978 fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
980 public FileHeader call() throws Exception {
981 return getService().createFile(u.getId(), folderLocal.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
985 fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
987 public FileHeader call() throws Exception {
988 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
991 updateAccounting(owner, new Date(), fileLocal.getCurrentBody().getFileSize());
992 getService().removeFileUploadProgress(user.getId(), fileName);
995 // We can't return 204 here since GWT's onSubmitComplete won't fire.
996 response.setContentType("text/html");
997 response.getWriter().print("<pre></pre>");
998 } catch (FileUploadException e) {
999 String error = "Error while uploading file";
1000 logger.error(error, e);
1001 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1002 } catch (GSSIOException e) {
1003 if (uploadedFile != null && uploadedFile.exists())
1004 uploadedFile.delete();
1005 String error = "Error while uploading file";
1007 logger.error(error, e);
1009 logger.debug(error, e);
1010 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1011 } catch (DuplicateNameException e) {
1012 if (uploadedFile != null && uploadedFile.exists())
1013 uploadedFile.delete();
1014 String error = "The specified file name already exists in this folder";
1015 logger.error(error, e);
1016 response.sendError(HttpServletResponse.SC_CONFLICT, error);
1018 } catch (InsufficientPermissionsException e) {
1019 if (uploadedFile != null && uploadedFile.exists())
1020 uploadedFile.delete();
1021 String error = "You don't have the necessary permissions";
1022 logger.error(error, e);
1023 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1025 } catch (QuotaExceededException e) {
1026 if (uploadedFile != null && uploadedFile.exists())
1027 uploadedFile.delete();
1028 String error = "Not enough free space available";
1029 if (logger.isDebugEnabled())
1030 logger.debug(error, e);
1031 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1033 } catch (ObjectNotFoundException e) {
1034 if (uploadedFile != null && uploadedFile.exists())
1035 uploadedFile.delete();
1036 String error = "A specified object was not found";
1037 logger.error(error, e);
1038 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1039 } catch (RpcException e) {
1040 if (uploadedFile != null && uploadedFile.exists())
1041 uploadedFile.delete();
1042 String error = "An error occurred while communicating with the service";
1043 logger.error(error, e);
1044 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1045 } catch (Exception e) {
1046 if (uploadedFile != null && uploadedFile.exists())
1047 uploadedFile.delete();
1048 String error = "An internal server error occurred";
1049 logger.error(error, e);
1050 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1055 * Move the resource in the specified path to the specified destination.
1057 * @param req the HTTP request
1058 * @param resp the HTTP response
1059 * @param path the path of the resource
1060 * @param moveTo the destination of the move procedure
1061 * @throws IOException if an input/output error occurs
1063 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1064 final User user = getUser(req);
1065 User owner = getOwner(req);
1066 Object resource = null;
1068 resource = getService().getResourceAtPath(owner.getId(), path, true);
1069 } catch (ObjectNotFoundException e) {
1070 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1072 } catch (RpcException e) {
1073 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1077 String destination = null;
1078 User destOwner = null;
1079 boolean exists = true;
1081 destination = getDestinationPath(req, encodePath(moveTo));
1082 destination = URLDecoder.decode(destination, "UTF-8");
1083 destOwner = getDestinationOwner(req);
1084 getService().getResourceAtPath(destOwner.getId(), destination, true);
1085 } catch (ObjectNotFoundException e) {
1087 } catch (URISyntaxException e) {
1088 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1090 } catch (RpcException e) {
1091 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1095 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1100 final User dOwner = destOwner;
1101 final String dest = destination;
1102 if (resource instanceof Folder) {
1103 final Folder folderLocal = (Folder) resource;
1104 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1106 public Void call() throws Exception {
1107 getService().moveFolderToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest);
1112 final FileHeader fileLocal = (FileHeader) resource;
1113 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1115 public Void call() throws Exception {
1116 getService().moveFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest);
1122 } catch (InsufficientPermissionsException e) {
1123 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1124 } catch (ObjectNotFoundException e) {
1125 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1126 } catch (RpcException e) {
1127 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1128 } catch (DuplicateNameException e) {
1129 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1130 } catch (GSSIOException e) {
1131 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1132 } catch (QuotaExceededException e) {
1133 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1134 } catch (Exception e) {
1135 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1140 * Copy the resource in the specified path to the specified destination.
1142 * @param req the HTTP request
1143 * @param resp the HTTP response
1144 * @param path the path of the resource
1145 * @param copyTo the destination of the copy procedure
1146 * @throws IOException if an input/output error occurs
1148 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1149 final User user = getUser(req);
1150 User owner = getOwner(req);
1151 Object resource = null;
1153 resource = getService().getResourceAtPath(owner.getId(), path, true);
1154 } catch (ObjectNotFoundException e) {
1155 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1157 } catch (RpcException e) {
1158 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1162 String destination = null;
1163 User destOwner = null;
1164 boolean exists = true;
1166 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1167 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1168 destOwner = getDestinationOwner(req);
1169 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1170 } catch (ObjectNotFoundException e) {
1172 } catch (URISyntaxException e) {
1173 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1175 } catch (RpcException e) {
1176 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1180 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1185 final User dOwner = destOwner;
1186 final String dest = destination;
1187 if (resource instanceof Folder) {
1188 final Folder folderLocal = (Folder) resource;
1189 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1191 public Void call() throws Exception {
1192 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest);
1197 final FileHeader fileLocal = (FileHeader) resource;
1198 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1200 public Void call() throws Exception {
1201 getService().copyFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest);
1206 } catch (InsufficientPermissionsException e) {
1207 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1208 } catch (ObjectNotFoundException e) {
1209 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1210 } catch (RpcException e) {
1211 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1212 } catch (DuplicateNameException e) {
1213 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1214 } catch (GSSIOException e) {
1215 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1216 } catch (QuotaExceededException e) {
1217 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1218 } catch (Exception e) {
1219 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1223 private String encodePath(String path) throws UnsupportedEncodingException{
1224 StringTokenizer str = new StringTokenizer(path, "/:", true);
1225 String result = new String();
1226 while(str.hasMoreTokens()){
1227 String token = str.nextToken();
1228 if(!token.equals("/") && !token.equals(":"))
1229 token = URLEncoder.encode(token,"UTF-8");
1230 result = result + token;
1235 * A helper method that extracts the relative resource path,
1236 * after removing the 'files' namespace.
1237 * The path returned is <i>not</i> URL-decoded.
1239 * @param req the HTTP request
1240 * @param path the specified path
1241 * @return the path relative to the root folder
1242 * @throws URISyntaxException
1243 * @throws RpcException in case an error occurs while communicating
1245 * @throws UnsupportedEncodingException
1247 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1248 URI uri = new URI(path);
1249 String dest = uri.getRawPath();
1250 // Remove the context path from the destination URI.
1251 String contextPath = req.getContextPath();
1252 if (!dest.startsWith(contextPath))
1253 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1254 dest = dest.substring(contextPath.length());
1255 // Remove the servlet path from the destination URI.
1256 String servletPath = req.getServletPath();
1257 if (!dest.startsWith(servletPath))
1258 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1259 dest = dest.substring(servletPath.length());
1260 // Strip the username part
1261 if (dest.length() < 2)
1262 throw new URISyntaxException(dest, "No username in the destination URI");
1263 int slash = dest.substring(1).indexOf('/');
1265 throw new URISyntaxException(dest, "No username in the destination URI");
1266 // Decode the user to get the proper characters (mainly the @)
1267 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1269 o = getService().findUser(owner);
1271 throw new URISyntaxException(dest, "User " + owner + " not found");
1273 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1274 dest = dest.substring(slash + 1);
1276 // Chop the resource namespace part
1277 dest = dest.substring(RequestHandler.PATH_FILES.length());
1279 dest = dest.endsWith("/")? dest: dest + '/';
1284 * Move the resource in the specified path to the trash bin.
1286 * @param req the HTTP request
1287 * @param resp the HTTP response
1288 * @param path the path of the resource
1289 * @throws IOException if an input/output error occurs
1291 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1292 final User user = getUser(req);
1293 User owner = getOwner(req);
1294 Object resource = null;
1296 resource = getService().getResourceAtPath(owner.getId(), path, true);
1297 } catch (ObjectNotFoundException e) {
1298 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1300 } catch (RpcException e) {
1301 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1306 if (resource instanceof Folder) {
1307 final Folder folderLocal = (Folder) resource;
1308 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1310 public Void call() throws Exception {
1311 getService().moveFolderToTrash(user.getId(), folderLocal.getId());
1316 final FileHeader fileLocal = (FileHeader) resource;
1317 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1319 public Void call() throws Exception {
1320 getService().moveFileToTrash(user.getId(), fileLocal.getId());
1325 } catch (InsufficientPermissionsException e) {
1326 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1327 } catch (ObjectNotFoundException e) {
1328 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1329 } catch (RpcException e) {
1330 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1331 } catch (Exception e) {
1332 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1337 * Restore the resource in the specified path from the trash bin.
1339 * @param req the HTTP request
1340 * @param resp the HTTP response
1341 * @param path the path of the resource
1342 * @throws IOException if an input/output error occurs
1344 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1345 final User user = getUser(req);
1346 User owner = getOwner(req);
1347 Object resource = null;
1349 resource = getService().getResourceAtPath(owner.getId(), path, false);
1350 } catch (ObjectNotFoundException e) {
1351 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1353 } catch (RpcException e) {
1354 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1359 if (resource instanceof Folder) {
1360 final Folder folderLocal = (Folder) resource;
1361 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1363 public Void call() throws Exception {
1364 getService().removeFolderFromTrash(user.getId(), folderLocal.getId());
1369 final FileHeader fileLocal = (FileHeader) resource;
1370 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1372 public Void call() throws Exception {
1373 getService().removeFileFromTrash(user.getId(), fileLocal.getId());
1378 } catch (InsufficientPermissionsException e) {
1379 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1380 } catch (ObjectNotFoundException e) {
1381 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1382 } catch (RpcException e) {
1383 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1384 } catch (Exception e) {
1385 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1390 * Update the resource in the specified path.
1392 * @param req the HTTP request
1393 * @param resp the HTTP response
1394 * @param path the path of the resource
1395 * @throws IOException if an input/output error occurs
1397 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1398 final User user = getUser(req);
1399 User owner = getOwner(req);
1400 Object resource = null;
1403 resource = getService().getResourceAtPath(owner.getId(), path, false);
1404 } catch (ObjectNotFoundException e) {
1405 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1407 } catch (RpcException e) {
1408 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1411 StringBuffer input = new StringBuffer();
1412 JSONObject json = null;
1413 if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1414 input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1416 // Assume application/json
1417 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1419 while ((line = reader.readLine()) != null)
1424 json = new JSONObject(input.toString());
1425 if (logger.isDebugEnabled())
1426 logger.debug("JSON update: " + json);
1427 if (resource instanceof Folder) {
1428 final Folder folderLocal = (Folder) resource;
1429 String name = json.optString("name");
1430 if (!isValidResourceName(name)) {
1431 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1434 JSONArray permissions = json.optJSONArray("permissions");
1435 Set<Permission> perms = null;
1436 if (permissions != null)
1437 perms = parsePermissions(user, permissions);
1438 Boolean readForAll = null;
1439 if (json.opt("readForAll") != null)
1440 readForAll = json.optBoolean("readForAll");
1441 if (!name.isEmpty() || permissions != null || readForAll != null) {
1442 final String fName = name.isEmpty()? null: name;
1443 final Boolean freadForAll = readForAll;
1444 final Set<Permission> fPerms = perms;
1445 Folder folderUpdated = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() {
1447 public Folder call() throws Exception {
1448 return getService().updateFolder(user.getId(), folderLocal.getId(), fName, freadForAll, fPerms);
1452 resp.getWriter().println(getNewUrl(req, folderUpdated));
1455 final FileHeader fileLocal = (FileHeader) resource;
1457 if (json.opt("name") != null)
1458 name = json.optString("name");
1460 if (!isValidResourceName(name)) {
1461 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1464 Long modificationDate = null;
1465 if (json.optLong("modificationDate") != 0)
1466 modificationDate = json.optLong("modificationDate");
1467 Boolean versioned = null;
1468 if (json.opt("versioned") != null)
1469 versioned = json.getBoolean("versioned");
1470 JSONArray tagset = json.optJSONArray("tags");
1472 StringBuffer t = new StringBuffer();
1473 if (tagset != null) {
1474 for (int i = 0; i < tagset.length(); i++)
1475 t.append(tagset.getString(i) + ',');
1476 tags = t.toString();
1478 JSONArray permissions = json.optJSONArray("permissions");
1479 Set<Permission> perms = null;
1480 if (permissions != null)
1481 perms = parsePermissions(user, permissions);
1482 Boolean readForAll = null;
1483 if (json.opt("readForAll") != null)
1484 readForAll = json.optBoolean("readForAll");
1485 if (name != null || tags != null || modificationDate != null
1486 || versioned != null || perms != null
1487 || readForAll != null) {
1488 final String fName = name;
1489 final String fTags = tags;
1490 final Date mDate = modificationDate != null? new Date(modificationDate): null;
1491 final Boolean fVersioned = versioned;
1492 final Boolean fReadForAll = readForAll;
1493 final Set<Permission> fPerms = perms;
1494 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1496 public Object call() throws Exception {
1497 getService().updateFile(user.getId(), fileLocal.getId(),
1498 fName, fTags, mDate, fVersioned,
1499 fReadForAll, fPerms);
1506 } catch (JSONException e) {
1507 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1508 } catch (InsufficientPermissionsException e) {
1509 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1510 } catch (ObjectNotFoundException e) {
1511 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1512 } catch (DuplicateNameException e) {
1513 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1514 } catch (RpcException e) {
1515 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1516 } catch (Exception e) {
1517 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1523 * Returns the new URL of an updated folder.
1525 private String getNewUrl(HttpServletRequest req, Folder folder) throws UnsupportedEncodingException {
1526 String parentUrl = URLDecoder.decode(getContextPath(req, true),"UTF-8");
1527 String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8");
1528 if (parentUrl.indexOf(fpath) != -1)
1529 parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1530 if(!parentUrl.endsWith("/"))
1531 parentUrl = parentUrl+"/";
1532 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1537 * Helper method to convert a JSON array of permissions into a set of
1538 * Permission objects.
1540 * @param user the current user
1541 * @param permissions the JSON array to parse
1542 * @return the parsed set of permissions
1543 * @throws JSONException if there was an error parsing the JSON object
1544 * @throws RpcException if there was an error communicating with the EJB
1545 * @throws ObjectNotFoundException if the user could not be found
1546 * @throws UnsupportedEncodingException
1548 private Set<Permission> parsePermissions(User user, JSONArray permissions)
1549 throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1550 if (permissions == null)
1552 Set<Permission> perms = new HashSet<Permission>();
1553 for (int i = 0; i < permissions.length(); i++) {
1554 JSONObject j = permissions.getJSONObject(i);
1555 Permission perm = new Permission();
1556 perm.setModifyACL(j.optBoolean("modifyACL"));
1557 perm.setRead(j.optBoolean("read"));
1558 perm.setWrite(j.optBoolean("write"));
1559 String permUser = j.optString("user");
1560 if (!permUser.isEmpty()) {
1561 User u = getService().findUser(permUser);
1563 throw new ObjectNotFoundException("User " + permUser + " not found");
1566 // 31/8/2009: Add optional groupUri which takes priority if it exists
1567 String permGroupUri = j.optString("groupUri");
1568 String permGroup = j.optString("group");
1569 if (!permGroupUri.isEmpty()) {
1570 String[] names = permGroupUri.split("/");
1571 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1572 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1573 User u = getService().findUser(usr);
1575 throw new ObjectNotFoundException("User " + permUser + " not found");
1576 Group g = getService().getGroup(u.getId(), grp);
1579 else if (!permGroup.isEmpty()) {
1580 Group g = getService().getGroup(user.getId(), permGroup);
1583 if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1584 throw new JSONException("A permission must correspond to either a user or a group");
1591 * Creates a new folder with the specified name under the folder in the provided path.
1593 * @param req the HTTP request
1594 * @param resp the HTTP response
1595 * @param path the parent folder path
1596 * @param folderName the name of the new folder
1597 * @throws IOException if an input/output error occurs
1599 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1600 if (logger.isDebugEnabled())
1601 logger.debug("Creating folder " + folderName + " in '" + path);
1603 final User user = getUser(req);
1604 User owner = getOwner(req);
1605 boolean exists = true;
1607 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1608 } catch (ObjectNotFoundException e) {
1610 } catch (RpcException e) {
1611 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1616 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1617 ", " + METHOD_HEAD);
1618 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1624 parent = getService().getResourceAtPath(owner.getId(), path, true);
1625 } catch (ObjectNotFoundException e) {
1626 resp.sendError(HttpServletResponse.SC_CONFLICT);
1628 } catch (RpcException e) {
1629 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1633 if (parent instanceof Folder) {
1634 final Folder folderLocal = (Folder) parent;
1635 Folder newFolder = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() {
1637 public Folder call() throws Exception {
1638 return getService().createFolder(user.getId(), folderLocal.getId(), folderName);
1642 String newResource = getApiRoot() + newFolder.getURI();
1643 resp.setHeader("Location", newResource);
1644 resp.setContentType("text/plain");
1645 PrintWriter out = resp.getWriter();
1646 out.println(newResource);
1648 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1651 } catch (DuplicateNameException e) {
1652 resp.sendError(HttpServletResponse.SC_CONFLICT);
1654 } catch (InsufficientPermissionsException e) {
1655 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1657 } catch (ObjectNotFoundException e) {
1658 resp.sendError(HttpServletResponse.SC_CONFLICT);
1660 } catch (RpcException e) {
1661 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1663 } catch (Exception e) {
1664 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1667 resp.setStatus(HttpServletResponse.SC_CREATED);
1673 * @throws IOException
1674 * @throws FileNotFoundException
1676 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1677 String path = getInnerPath(req, PATH_FILES);
1679 path = URLDecoder.decode(path, "UTF-8");
1680 } catch (IllegalArgumentException e) {
1681 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1684 if (logger.isDebugEnabled())
1685 logger.debug("Updating resource: " + path);
1687 final User user = getUser(req);
1688 User owner = getOwner(req);
1689 boolean exists = true;
1690 Object resource = null;
1691 FileHeader fileLocal = null;
1693 resource = getService().getResourceAtPath(owner.getId(), path, false);
1694 } catch (ObjectNotFoundException e) {
1696 } catch (RpcException e) {
1697 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1702 if (resource instanceof FileHeader)
1703 fileLocal = (FileHeader) resource;
1705 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1708 boolean result = true;
1710 // Temporary content file used to support partial PUT.
1711 File contentFile = null;
1713 Range range = parseContentRange(req, resp);
1715 InputStream resourceInputStream = null;
1717 // Append data specified in ranges to existing content for this
1718 // resource - create a temporary file on the local filesystem to
1719 // perform this operation.
1720 // Assume just one range is specified for now
1721 if (range != null) {
1723 contentFile = executePartialPut(req, range, path);
1724 } catch (RpcException e) {
1725 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1727 } catch (ObjectNotFoundException e) {
1728 resp.sendError(HttpServletResponse.SC_CONFLICT);
1730 } catch (InsufficientPermissionsException e) {
1731 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1734 resourceInputStream = new FileInputStream(contentFile);
1736 resourceInputStream = req.getInputStream();
1739 Folder folderLocal = null;
1740 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1741 if (!(parent instanceof Folder)) {
1742 resp.sendError(HttpServletResponse.SC_CONFLICT);
1745 folderLocal = (Folder) parent;
1746 final String name = getLastElement(path);
1747 final String mimeType = context.getMimeType(name);
1748 File uploadedFile = null;
1750 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1751 } catch (IOException ex) {
1752 throw new GSSIOException(ex, false);
1754 FileHeader fileTemp = null;
1755 final File uploadedf = uploadedFile;
1756 final Folder parentf = folderLocal;
1757 final FileHeader f = fileLocal;
1759 fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
1761 public FileHeader call() throws Exception {
1762 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1766 fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() {
1768 public FileHeader call() throws Exception {
1769 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1773 updateAccounting(owner, new Date(), fileTemp.getCurrentBody().getFileSize());
1774 getService().removeFileUploadProgress(user.getId(), fileTemp.getName());
1775 } catch(ObjectNotFoundException e) {
1777 } catch (RpcException e) {
1778 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1780 } catch (IOException e) {
1781 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1783 } catch (GSSIOException e) {
1784 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1786 } catch (DuplicateNameException e) {
1787 resp.sendError(HttpServletResponse.SC_CONFLICT);
1789 } catch (InsufficientPermissionsException e) {
1790 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1792 } catch (QuotaExceededException e) {
1793 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1795 } catch (Exception e) {
1796 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1802 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1804 resp.setStatus(HttpServletResponse.SC_CREATED);
1806 resp.sendError(HttpServletResponse.SC_CONFLICT);
1810 * Delete a resource.
1812 * @param req The servlet request we are processing
1813 * @param resp The servlet response we are processing
1814 * @throws IOException if the response cannot be sent
1816 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1817 String path = getInnerPath(req, PATH_FILES);
1818 if (logger.isDebugEnabled())
1819 logger.debug("Deleting resource '" + path);
1820 path = URLDecoder.decode(path, "UTF-8");
1821 final User user = getUser(req);
1822 User owner = getOwner(req);
1823 boolean exists = true;
1824 Object object = null;
1826 object = getService().getResourceAtPath(owner.getId(), path, false);
1827 } catch (ObjectNotFoundException e) {
1829 } catch (RpcException e) {
1830 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1835 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1839 Folder folderLocal = null;
1840 FileHeader fileLocal = null;
1841 if (object instanceof Folder)
1842 folderLocal = (Folder) object;
1844 fileLocal = (FileHeader) object;
1846 if (fileLocal != null)
1848 final FileHeader f = fileLocal;
1849 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1851 public Void call() throws Exception {
1852 getService().deleteFile(user.getId(), f.getId());
1856 } catch (InsufficientPermissionsException e) {
1857 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1859 } catch (ObjectNotFoundException e) {
1860 // Although we had already found the object, it was
1861 // probably deleted from another thread.
1862 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1864 } catch (RpcException e) {
1865 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1867 } catch (Exception e) {
1868 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1871 else if (folderLocal != null)
1873 final Folder fo = folderLocal;
1874 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1876 public Void call() throws Exception {
1877 getService().deleteFolder(user.getId(), fo.getId());
1881 } catch (InsufficientPermissionsException e) {
1882 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1884 } catch (ObjectNotFoundException e) {
1885 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1887 } catch (RpcException e) {
1888 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1890 } catch (Exception e) {
1891 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1894 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1899 * Return an InputStream to a JSON representation of the contents
1900 * of this directory.
1902 * @param user the user that made the request
1903 * @param folder the specified directory
1904 * @return an input stream with the rendered contents
1905 * @throws IOException if the response cannot be sent
1906 * @throws ServletException
1907 * @throws InsufficientPermissionsException if the user does not have
1908 * the necessary privileges to read the directory
1910 private InputStream renderJson(User user, Folder folder) throws IOException,
1911 ServletException, InsufficientPermissionsException {
1913 folder = getService().expandFolder(folder);
1914 } catch (ObjectNotFoundException e1) {
1915 // TODO Auto-generated catch block
1916 e1.printStackTrace();
1917 } catch (RpcException e1) {
1918 // TODO Auto-generated catch block
1919 e1.printStackTrace();
1921 JSONObject json = new JSONObject();
1923 json.put("name", folder.getName()).
1924 put("owner", folder.getOwner().getUsername()).
1925 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1926 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1927 put("deleted", folder.isDeleted()).
1928 put("shared", folder.getShared()).
1929 put("readForAll", folder.isReadForAll());
1931 if (folder.getAuditInfo().getModifiedBy() != null)
1932 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1933 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1934 if (folder.getParent() != null) {
1935 JSONObject j = new JSONObject();
1936 j.put("uri", getApiRoot() + folder.getParent().getURI());
1937 j.put("name", folder.getParent().getName());
1938 json.put("parent", j);
1940 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1941 for (Folder f: folder.getSubfolders())
1942 if (!f.isDeleted()) {
1943 JSONObject j = new JSONObject();
1944 j.put("name", f.getName()).
1945 put("uri", getApiRoot() + f.getURI());
1948 json.put("folders", subfolders);
1949 List<JSONObject> files = new ArrayList<JSONObject>();
1950 List<FileHeader> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1951 for (FileHeader f: fileHeaders) {
1952 JSONObject j = new JSONObject();
1953 j.put("name", f.getName()).
1954 put("owner", f.getOwner().getUsername()).
1955 put("deleted", f.isDeleted()).
1956 put("version", f.getCurrentBody().getVersion()).
1957 put("content", f.getCurrentBody().getMimeType()).
1958 put("size", f.getCurrentBody().getFileSize()).
1959 put("shared", f.getShared()).
1960 put("versioned",f.isVersioned()).
1961 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1962 put("path", f.getFolder().getPath()).
1963 put("uri", getApiRoot() + f.getURI());
1964 if (f.getAuditInfo().getModificationDate() != null)
1965 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1968 json.put("files", files);
1969 Set<Permission> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1970 json.put("permissions", renderJson(perms));
1971 } catch (JSONException e) {
1972 throw new ServletException(e);
1973 } catch (ObjectNotFoundException e) {
1974 throw new ServletException(e);
1975 } catch (RpcException e) {
1976 throw new ServletException(e);
1979 // Prepare a writer to a buffered area
1980 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1981 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1982 PrintWriter writer = new PrintWriter(osWriter);
1984 // Return an input stream to the underlying bytes
1985 writer.write(json.toString());
1987 return new ByteArrayInputStream(stream.toByteArray());
1991 * Return a String with a JSON representation of the metadata
1992 * of the specified folder.
1993 * @throws RpcException
1994 * @throws InsufficientPermissionsException
1995 * @throws ObjectNotFoundException
1997 private String renderJsonMetadata(User user, Folder folder)
1998 throws ServletException, InsufficientPermissionsException {
1999 // Check if the user has read permission.
2001 if (!getService().canReadFolder(user.getId(), folder.getId()))
2002 throw new InsufficientPermissionsException();
2003 } catch (ObjectNotFoundException e) {
2004 throw new ServletException(e);
2005 } catch (RpcException e) {
2006 throw new ServletException(e);
2009 JSONObject json = new JSONObject();
2011 json.put("name", URLEncoder.encode(folder.getName(), "UTF-8")).
2012 put("owner", folder.getOwner().getUsername()).
2013 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
2014 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
2015 put("deleted", folder.isDeleted());
2016 if (folder.getAuditInfo().getModifiedBy() != null)
2017 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
2018 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
2019 } catch (JSONException e) {
2020 throw new ServletException(e);
2022 catch (UnsupportedEncodingException e) {
2023 throw new ServletException(e);
2025 return json.toString();
2029 * Return a String with a JSON representation of the metadata
2030 * of the specified file. If an old file body is provided, then
2031 * the metadata of that particular version will be returned.
2033 * @param user the user that made the request
2034 * @param file the specified file header
2035 * @param oldBody the version number
2036 * @return the JSON-encoded file
2037 * @throws ServletException
2038 * @throws InsufficientPermissionsException if the user does not have
2039 * the necessary privileges to read the directory
2041 private String renderJson(User user, FileHeader file, FileBody oldBody)
2042 throws ServletException, InsufficientPermissionsException {
2043 JSONObject json = new JSONObject();
2045 file=getService().expandFile(file);
2046 // Need to encode file name in order to properly display it in the web client.
2047 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2048 put("owner", file.getOwner().getUsername()).
2049 put("versioned", file.isVersioned()).
2050 put("version", oldBody != null ? oldBody.getVersion() : file.getCurrentBody().getVersion()).
2051 put("readForAll", file.isReadForAll()).
2052 put("shared", file.getShared()).
2053 put("tags", renderJson(file.getFileTagsAsStrings())).
2054 put("path", file.getFolder().getPath()).
2055 put("uri", getApiRoot() + file.getURI()).
2056 put("deleted", file.isDeleted());
2057 JSONObject j = new JSONObject();
2058 j.put("uri", getApiRoot() + file.getFolder().getURI()).
2059 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2060 json.put("folder", j);
2061 if (oldBody != null)
2062 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2063 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2064 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2065 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2066 put("content", oldBody.getMimeType()).
2067 put("size", oldBody.getFileSize());
2069 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2070 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2071 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2072 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2073 put("content", file.getCurrentBody().getMimeType()).
2074 put("size", file.getCurrentBody().getFileSize());
2075 Set<Permission> perms = getService().getFilePermissions(user.getId(), file.getId());
2076 json.put("permissions", renderJson(perms));
2077 } catch (JSONException e) {
2078 throw new ServletException(e);
2079 } catch (ObjectNotFoundException e) {
2080 throw new ServletException(e);
2081 } catch (RpcException e) {
2082 throw new ServletException(e);
2083 } catch (UnsupportedEncodingException e) {
2084 throw new ServletException(e);
2087 return json.toString();
2091 * Return a String with a JSON representation of the
2092 * specified set of permissions.
2094 * @param permissions the set of permissions
2095 * @return the JSON-encoded object
2096 * @throws JSONException
2097 * @throws UnsupportedEncodingException
2099 private JSONArray renderJson(Set<Permission> permissions) throws JSONException, UnsupportedEncodingException {
2100 JSONArray perms = new JSONArray();
2101 for (Permission p: permissions) {
2102 JSONObject permission = new JSONObject();
2103 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2104 if (p.getUser() != null)
2105 permission.put("user", p.getUser().getUsername());
2106 if (p.getGroup() != null) {
2107 Group group = p.getGroup();
2108 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2109 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2111 perms.put(permission);
2117 * Return a String with a JSON representation of the
2118 * specified collection of tags.
2120 * @param tags the collection of tags
2121 * @return the JSON-encoded object
2122 * @throws JSONException
2123 * @throws UnsupportedEncodingException
2125 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2126 JSONArray tagArray = new JSONArray();
2127 for (String t: tags)
2128 tagArray.put(URLEncoder.encode(t,"UTF-8"));
2133 * Retrieves the user who owns the destination namespace, for a
2134 * copy or move request.
2136 * @param req the HTTP request
2137 * @return the owner of the namespace
2139 protected User getDestinationOwner(HttpServletRequest req) {
2140 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2144 * A helper inner class for updating the progress status of a file upload.
2148 public static class StatusProgressListener implements ProgressListener {
2149 private int percentLogged = 0;
2150 private long bytesTransferred = 0;
2152 private long fileSize = -100;
2154 private Long userId;
2156 private String filename;
2158 private ExternalAPI service;
2160 public StatusProgressListener(ExternalAPI aService) {
2165 * Modify the userId.
2167 * @param aUserId the userId to set
2169 public void setUserId(Long aUserId) {
2174 * Modify the filename.
2176 * @param aFilename the filename to set
2178 public void setFilename(String aFilename) {
2179 filename = aFilename;
2183 public void update(long bytesRead, long contentLength, int items) {
2184 //monitoring per percent of bytes uploaded
2185 bytesTransferred = bytesRead;
2186 if (fileSize != contentLength)
2187 fileSize = contentLength;
2188 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2189 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2190 if (percent != percentLogged){
2191 percentLogged = percent;
2193 if (userId != null && filename != null)
2194 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2195 } catch (ObjectNotFoundException e) {
2196 // Swallow the exception since it is going to be caught
2197 // by previously called methods
2204 * Return an InputStream to an HTML representation of the contents of this
2207 * @param contextPath Context path to which our internal paths are relative
2208 * @param path the requested path to the resource
2209 * @param folder the specified directory
2210 * @param user the specified user
2211 * @return an input stream with the rendered contents
2212 * @throws IOException
2213 * @throws ServletException
2215 private InputStream renderHtml(String contextPath, String path, Folder folder, User user)
2216 throws IOException, ServletException {
2217 String name = folder.getName();
2218 // Prepare a writer to a buffered area
2219 ByteArrayOutputStream stream = new ByteArrayOutputStream();
2220 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2221 PrintWriter writer = new PrintWriter(osWriter);
2222 StringBuffer sb = new StringBuffer();
2223 // rewriteUrl(contextPath) is expensive. cache result for later reuse
2224 String rewrittenContextPath = rewriteUrl(contextPath);
2225 // Render the page header
2226 sb.append("<html>\r\n");
2227 sb.append("<head>\r\n");
2228 sb.append("<title>");
2229 sb.append("Index of " + name);
2230 sb.append("</title>\r\n");
2231 sb.append("<STYLE><!--");
2233 sb.append("--></STYLE> ");
2234 sb.append("</head>\r\n");
2235 sb.append("<body>");
2237 sb.append("Index of " + name);
2239 // Render the link to our parent (if required)
2240 String parentDirectory = path;
2241 if (parentDirectory.endsWith("/"))
2242 parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2243 int slash = parentDirectory.lastIndexOf('/');
2245 String parent = path.substring(0, slash);
2246 sb.append(" - <a href=\"");
2247 sb.append(rewrittenContextPath);
2248 if (parent.equals(""))
2251 if (!parent.endsWith("/"))
2255 sb.append("Up To " + parent);
2261 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2263 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
2265 // Render the column headings
2266 sb.append("<tr>\r\n");
2267 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2269 sb.append("</strong></font></td>\r\n");
2270 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2272 sb.append("</strong></font></td>\r\n");
2273 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2274 sb.append("Last modified");
2275 sb.append("</strong></font></td>\r\n");
2277 // Render the directory entries within this directory
2278 boolean shade = false;
2279 Iterator iter = folder.getSubfolders().iterator();
2280 while (iter.hasNext()) {
2281 Folder subf = (Folder) iter.next();
2282 if(subf.isReadForAll() && !subf.isDeleted()){
2283 String resourceName = subf.getName();
2284 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2289 sb.append(" bgcolor=\"#eeeeee\"");
2293 sb.append("<td align=\"left\"> \r\n");
2294 sb.append("<a href=\"");
2295 sb.append(rewrittenContextPath+path);
2296 sb.append(rewriteUrl(resourceName));
2298 sb.append("\"><tt>");
2299 sb.append(RequestUtil.filter(resourceName));
2301 sb.append("</tt></a></td>\r\n");
2303 sb.append("<td align=\"right\"><tt>");
2304 sb.append(" ");
2305 sb.append("</tt></td>\r\n");
2307 sb.append("<td align=\"right\"><tt>");
2308 sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2309 sb.append("</tt></td>\r\n");
2311 sb.append("</tr>\r\n");
2315 List<FileHeader> files;
2317 files = getService().getFiles(user.getId(), folder.getId(), true);
2318 } catch (ObjectNotFoundException e) {
2319 throw new ServletException(e.getMessage());
2320 } catch (InsufficientPermissionsException e) {
2321 throw new ServletException(e.getMessage());
2322 } catch (RpcException e) {
2323 throw new ServletException(e.getMessage());
2325 for (FileHeader file : files)
2326 //Display only file resources that are marked as public and are not deleted
2327 if(file.isReadForAll() && !file.isDeleted()){
2328 String resourceName = file.getName();
2329 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2334 sb.append(" bgcolor=\"#eeeeee\"");
2338 sb.append("<td align=\"left\"> \r\n");
2339 sb.append("<a href=\"");
2340 sb.append(rewrittenContextPath + path);
2341 sb.append(rewriteUrl(resourceName));
2342 sb.append("\"><tt>");
2343 sb.append(RequestUtil.filter(resourceName));
2344 sb.append("</tt></a></td>\r\n");
2346 sb.append("<td align=\"right\"><tt>");
2347 sb.append(renderSize(file.getCurrentBody().getFileSize()));
2348 sb.append("</tt></td>\r\n");
2350 sb.append("<td align=\"right\"><tt>");
2351 sb.append(getLastModifiedHttp(file.getAuditInfo()));
2352 sb.append("</tt></td>\r\n");
2354 sb.append("</tr>\r\n");
2357 // Render the page footer
2358 sb.append("</table>\r\n");
2360 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2361 sb.append("</body>\r\n");
2362 sb.append("</html>\r\n");
2364 // Return an input stream to the underlying bytes
2365 writer.write(sb.toString());
2367 return new ByteArrayInputStream(stream.toByteArray());