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 boolean exists = true;
207 Object resource = null;
208 FileHeaderDTO file = null;
209 FolderDTO 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 FolderDTO)
227 folder = (FolderDTO) resource;
229 file = (FileHeaderDTO) 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) {
359 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
362 serveProgress(req, resp, progress, user, file);
366 // Fetch the version to retrieve, if specified.
367 String verStr = req.getParameter(VERSION_PARAM);
369 FileBodyDTO oldBody = null;
370 if (verStr != null && file != null)
372 version = Integer.valueOf(verStr);
373 } catch (NumberFormatException e) {
374 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI());
379 oldBody = getService().getFileVersion(user.getId(), file.getId(), version);
380 } catch (RpcException e) {
381 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
383 } catch (ObjectNotFoundException e) {
384 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
386 } catch (InsufficientPermissionsException e) {
387 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
391 // Check if the conditions specified in the optional If headers are
392 // satisfied. Doing this for folders would require recursive checking
393 // for all of their children, which in turn would defy the purpose of
396 // Checking If headers.
397 if (!checkIfHeaders(req, resp, file, oldBody))
400 else if(!checkIfModifiedSince(req, resp, folder))
403 // Find content type.
404 String contentType = null;
405 boolean isContentHtml = false;
406 boolean expectJSON = false;
409 contentType = version>0 ? oldBody.getMimeType() : file.getMimeType();
410 if (contentType == null) {
411 contentType = context.getMimeType(file.getName());
412 file.setMimeType(contentType);
414 } else { // folder != null
415 String accept = req.getHeader("Accept");
416 // The order in this conditional pessimizes the common API case,
417 // but is important for backwards compatibility with existing
418 // clients who send no accept header and expect a JSON response.
419 if (accept != null && accept.contains("text/html")) {
420 contentType = "text/html;charset=UTF-8";
421 isContentHtml = true;
422 //this is the case when clients send the appropriate headers, the contentType is "text/html"
423 //and expect a JSON response. The above check applies to FireGSS client
424 expectJSON = !authDeferred ? true : false;
427 contentType = "application/json;charset=UTF-8";
433 ArrayList ranges = null;
434 long contentLength = -1L;
437 // Parse range specifier.
438 ranges = parseRange(req, resp, file, oldBody);
440 resp.setHeader("ETag", getETag(file, oldBody));
441 // Last-Modified header.
442 String lastModified = oldBody == null ?
443 getLastModifiedHttp(file.getAuditInfo()) :
444 getLastModifiedHttp(oldBody.getAuditInfo());
445 resp.setHeader("Last-Modified", lastModified);
446 // X-GSS-Metadata header.
448 resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody));
449 } catch (InsufficientPermissionsException e) {
450 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
453 // Get content length.
454 contentLength = version>0 ? oldBody.getFileSize() : file.getFileSize();
455 // Special case for zero length files, which would cause a
456 // (silent) ISE when setting the output buffer size.
457 if (contentLength == 0L)
460 // Set the folder X-GSS-Metadata header.
462 resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder));
463 } catch (InsufficientPermissionsException e) {
464 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
468 ServletOutputStream ostream = null;
469 PrintWriter writer = null;
473 ostream = resp.getOutputStream();
474 } catch (IllegalStateException e) {
475 // If it fails, we try to get a Writer instead if we're
476 // trying to serve a text file
477 if ( contentType == null
478 || contentType.startsWith("text")
479 || contentType.endsWith("xml") )
480 writer = resp.getWriter();
484 if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) {
485 // Set the appropriate output headers
486 if (contentType != null) {
487 if (logger.isDebugEnabled())
488 logger.debug("contentType='" + contentType + "'");
489 resp.setContentType(contentType);
491 if (file != null && contentLength >= 0) {
492 if (logger.isDebugEnabled())
493 logger.debug("contentLength=" + contentLength);
494 if (contentLength < Integer.MAX_VALUE)
495 resp.setContentLength((int) contentLength);
498 // Set the content-length as String to be able to use a long
499 resp.setHeader("content-length", "" + contentLength);
502 InputStream renderResult = null;
503 String relativePath = getRelativePath(req);
504 String contextPath = req.getContextPath();
505 String servletPath = req.getServletPath();
506 String contextServletPath = contextPath + servletPath;
507 if (folder != null && content)
508 // Serve the directory browser for a public folder
509 if (isContentHtml && !expectJSON)
510 renderResult = renderHtml(contextServletPath, relativePath, folder,user);
511 // Serve the directory for an ordinary folder or for fireGSS client
514 renderResult = renderJson(user, folder);
515 } catch (InsufficientPermissionsException e) {
516 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
521 // Copy the input stream to our output stream (if requested)
524 resp.setBufferSize(output);
525 } catch (IllegalStateException e) {
530 if (needsContentDisposition(req))
531 resp.setHeader("Content-Disposition","attachment; filename*=UTF-8''"+getDispositionFilename(file));
533 resp.setHeader("Content-Disposition","inline; filename*=UTF-8''"+getDispositionFilename(file));
535 copy(file, renderResult, ostream, req, oldBody);
537 copy(file, renderResult, writer, req, oldBody);
538 if (file!=null) updateAccounting(owner, new Date(), contentLength);
539 } catch (ObjectNotFoundException e) {
540 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
542 } catch (InsufficientPermissionsException e) {
543 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
545 } catch (RpcException e) {
546 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
551 if (ranges == null || ranges.isEmpty())
553 // Partial content response.
554 resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
556 if (ranges.size() == 1) {
557 Range range = (Range) ranges.get(0);
558 resp.addHeader("Content-Range", "bytes "
560 + "-" + range.end + "/"
562 long length = range.end - range.start + 1;
563 if (length < Integer.MAX_VALUE)
564 resp.setContentLength((int) length);
566 // Set the content-length as String to be able to use a long
567 resp.setHeader("content-length", "" + length);
569 if (contentType != null) {
570 if (logger.isDebugEnabled())
571 logger.debug("contentType='" + contentType + "'");
572 resp.setContentType(contentType);
577 resp.setBufferSize(output);
578 } catch (IllegalStateException e) {
583 copy(file, ostream, range, req, oldBody);
585 copy(file, writer, range, req, oldBody);
586 updateAccounting(owner, new Date(), contentLength);
587 } catch (ObjectNotFoundException e) {
588 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
590 } catch (InsufficientPermissionsException e) {
591 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
593 } catch (RpcException e) {
594 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
599 resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation);
602 resp.setBufferSize(output);
603 } catch (IllegalStateException e) {
608 copy(file, ostream, ranges.iterator(), contentType, req, oldBody);
610 copy(file, writer, ranges.iterator(), contentType, req, oldBody);
611 updateAccounting(owner, new Date(), contentLength);
612 } catch (ObjectNotFoundException e) {
613 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
615 } catch (InsufficientPermissionsException e) {
616 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
618 } catch (RpcException e) {
619 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
628 * Handles an authentication failure. If no Authorization or Date request
629 * parameters and no Authorization, Date or X-GSS-Date headers were present,
630 * this is a browser request, so redirect to login and then let the user get
631 * back to the file. Otherwise it's a bogus client request and Forbidden is
634 private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException {
635 if (req.getParameter(AUTHORIZATION_PARAMETER) == null &&
636 req.getParameter(DATE_PARAMETER) == null &&
637 req.getHeader(AUTHORIZATION_HEADER) == null &&
638 req.getDateHeader(DATE_HEADER) == -1 &&
639 req.getDateHeader(GSS_DATE_HEADER) == -1)
640 resp.sendRedirect(getConfiguration().getString("loginUrl") +
641 "?next=" + req.getRequestURL().toString());
643 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
647 * Return the filename of the specified file properly formatted for
648 * including in the Content-Disposition header.
650 private String getDispositionFilename(FileHeaderDTO file) throws UnsupportedEncodingException {
651 return URLEncoder.encode(file.getName(),"UTF-8").replaceAll("\\+", "%20");
655 * Determines whether the user agent needs the Content-Disposition
656 * header to be set, in order to properly download a file.
658 * @param req the HTTP request
659 * @return true if the Content-Disposition HTTP header must be set
661 private boolean needsContentDisposition(HttpServletRequest req) {
662 /*String agent = req.getHeader("user-agent");
663 if (agent != null && agent.contains("MSIE"))
665 String dl = req.getParameter("dl");
672 * Sends a progress update on the amount of bytes received until now for
673 * a file that the current user is currently uploading.
675 * @param req the HTTP request
676 * @param resp the HTTP response
677 * @param parameter the value for the progress request parameter
678 * @param user the current user
679 * @param file the file being uploaded, or null if the request is about a new file
680 * @throws IOException if an I/O error occurs
682 private void serveProgress(HttpServletRequest req, HttpServletResponse resp,
683 String parameter, User user, FileHeaderDTO file) throws IOException {
684 String filename = file == null ? parameter : file.getName();
686 FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename);
687 if (status == null) {
688 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
691 JSONObject json = new JSONObject();
692 json.put("bytesUploaded", status.getBytesUploaded()).
693 put("bytesTotal", status.getFileSize());
694 sendJson(req, resp, json.toString());
696 // Workaround for IE's broken caching behavior.
697 resp.setHeader("Expires", "-1");
699 } catch (RpcException e) {
700 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
702 } catch (JSONException e) {
703 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
709 * Server a POST request to create/modify a file or folder.
711 * @param req the HTTP request
712 * @param resp the HTTP response
713 * @exception IOException if an input/output error occurs
715 void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
716 boolean authDeferred = getAuthDeferred(req);
717 if (!authDeferred && req.getParameterMap().size() > 1) {
718 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
721 String path = getInnerPath(req, PATH_FILES);
722 path = path.endsWith("/")? path: path + '/';
724 path = URLDecoder.decode(path, "UTF-8");
725 } catch (IllegalArgumentException e) {
726 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
729 // We only defer authenticating multipart POST requests.
731 if (!ServletFileUpload.isMultipartContent(req)) {
732 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
735 handleMultipart(req, resp, path);
739 String newName = req.getParameter(NEW_FOLDER_PARAMETER);
741 boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER);
742 boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER);
743 boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER);
744 String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER);
745 String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER);
746 String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER);
748 if (newName != null){
749 if (!isValidResourceName(newName)) {
750 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
753 createFolder(req, resp, path, newName);
755 else if (hasUpdateParam)
756 updateResource(req, resp, path);
757 else if (hasTrashParam)
758 trashResource(req, resp, path);
759 else if (hasRestoreParam)
760 restoreResource(req, resp, path);
761 else if (copyTo != null)
762 copyResource(req, resp, path, copyTo);
763 else if (moveTo != null)
764 moveResource(req, resp, path, moveTo);
765 else if (restoreVersion != null)
766 restoreVersion(req, resp, path, restoreVersion);
768 // IE with Gears uses POST for multiple uploads.
769 putResource(req, resp);
773 * Restores a previous version for a file.
775 * @param req the HTTP request
776 * @param resp the HTTP response
777 * @param path the resource path
778 * @param version the version number to restore
779 * @throws IOException if an I/O error occurs
781 private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException {
782 final User user = getUser(req);
783 User owner = getOwner(req);
784 Object resource = null;
786 resource = getService().getResourceAtPath(owner.getId(), path, true);
787 } catch (ObjectNotFoundException e) {
788 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
790 } catch (RpcException e) {
791 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
794 if (resource instanceof FolderDTO) {
795 resp.sendError(HttpServletResponse.SC_CONFLICT);
800 final FileHeaderDTO file = (FileHeaderDTO) resource;
801 final int oldVersion = Integer.parseInt(version);
803 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
805 public Void call() throws Exception {
806 getService().restoreVersion(user.getId(), file.getId(), oldVersion);
810 } catch (InsufficientPermissionsException e) {
811 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
812 } catch (ObjectNotFoundException e) {
813 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
814 } catch (RpcException e) {
815 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
816 } catch (GSSIOException e) {
817 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
818 } catch (QuotaExceededException e) {
819 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
820 } catch (NumberFormatException e) {
821 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
822 } catch (Exception e) {
823 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
828 * A method for handling multipart POST requests for uploading
829 * files from browser-based JavaScript clients.
831 * @param request the HTTP request
832 * @param response the HTTP response
833 * @param path the resource path
834 * @throws IOException in case an error occurs writing to the
837 private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
838 if (logger.isDebugEnabled())
839 logger.debug("Multipart POST for resource: " + path);
841 User owner = getOwner(request);
842 boolean exists = true;
843 Object resource = null;
844 FileHeaderDTO file = null;
846 resource = getService().getResourceAtPath(owner.getId(), path, false);
847 } catch (ObjectNotFoundException e) {
849 } catch (RpcException e) {
850 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
855 if (resource instanceof FileHeaderDTO) {
856 file = (FileHeaderDTO) resource;
857 if (file.isDeleted()) {
858 response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash");
862 response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
867 String parentPath = null;
869 parentPath = getParentPath(path);
870 parent = getService().getResourceAtPath(owner.getId(), parentPath, true);
871 } catch (ObjectNotFoundException e) {
872 response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath);
874 } catch (RpcException e) {
875 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
878 if (!(parent instanceof FolderDTO)) {
879 response.sendError(HttpServletResponse.SC_CONFLICT);
882 final FolderDTO folder = (FolderDTO) parent;
883 final String fileName = getLastElement(path);
885 FileItemIterator iter;
886 File uploadedFile = null;
888 // Create a new file upload handler.
889 ServletFileUpload upload = new ServletFileUpload();
890 StatusProgressListener progressListener = new StatusProgressListener(getService());
891 upload.setProgressListener(progressListener);
892 iter = upload.getItemIterator(request);
893 String dateParam = null;
895 while (iter.hasNext()) {
896 FileItemStream item = iter.next();
897 String name = item.getFieldName();
898 InputStream stream = item.openStream();
899 if (item.isFormField()) {
900 final String value = Streams.asString(stream);
901 if (name.equals(DATE_PARAMETER))
903 else if (name.equals(AUTHORIZATION_PARAMETER))
906 if (logger.isDebugEnabled())
907 logger.debug(name + ":" + value);
909 // Fetch the timestamp used to guard against replay attacks.
910 if (dateParam == null) {
911 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter");
917 timestamp = DateUtil.parseDate(dateParam).getTime();
918 } catch (DateParseException e) {
919 response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
922 if (!isTimeValid(timestamp)) {
923 response.sendError(HttpServletResponse.SC_FORBIDDEN);
927 // Fetch the Authorization parameter and find the user specified in it.
929 response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter");
932 String[] authParts = auth.split(" ");
933 if (authParts.length != 2) {
934 response.sendError(HttpServletResponse.SC_FORBIDDEN);
937 String username = authParts[0];
938 String signature = authParts[1];
941 user = getService().findUser(username);
942 } catch (RpcException e) {
943 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
947 response.sendError(HttpServletResponse.SC_FORBIDDEN);
950 request.setAttribute(USER_ATTRIBUTE, user);
952 // Remove the servlet path from the request URI.
953 String p = request.getRequestURI();
954 String servletPath = request.getContextPath() + request.getServletPath();
955 p = p.substring(servletPath.length());
956 // Validate the signature in the Authorization parameter.
957 String data = request.getMethod() + dateParam + p;
958 if (!isSignatureValid(signature, user, data)) {
959 response.sendError(HttpServletResponse.SC_FORBIDDEN);
963 progressListener.setUserId(user.getId());
964 progressListener.setFilename(fileName);
965 final String contentType = item.getContentType();
968 uploadedFile = getService().uploadFile(stream, user.getId());
969 } catch (IOException ex) {
970 throw new GSSIOException(ex, false);
972 FileHeaderDTO fileDTO = null;
973 final File upf = uploadedFile;
974 final FileHeaderDTO f = file;
977 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
979 public FileHeaderDTO call() throws Exception {
980 return getService().createFile(u.getId(), folder.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
984 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
986 public FileHeaderDTO call() throws Exception {
987 return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath());
990 updateAccounting(owner, new Date(), fileDTO.getFileSize());
991 getService().removeFileUploadProgress(user.getId(), fileName);
994 // We can't return 204 here since GWT's onSubmitComplete won't fire.
995 response.setContentType("text/html");
996 response.getWriter().print("<pre></pre>");
997 } catch (FileUploadException e) {
998 String error = "Error while uploading file";
999 logger.error(error, e);
1000 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1001 } catch (GSSIOException e) {
1002 if (uploadedFile != null && uploadedFile.exists())
1003 uploadedFile.delete();
1004 String error = "Error while uploading file";
1006 logger.error(error, e);
1008 logger.debug(error, e);
1009 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1010 } catch (DuplicateNameException e) {
1011 if (uploadedFile != null && uploadedFile.exists())
1012 uploadedFile.delete();
1013 String error = "The specified file name already exists in this folder";
1014 logger.error(error, e);
1015 response.sendError(HttpServletResponse.SC_CONFLICT, error);
1017 } catch (InsufficientPermissionsException e) {
1018 if (uploadedFile != null && uploadedFile.exists())
1019 uploadedFile.delete();
1020 String error = "You don't have the necessary permissions";
1021 logger.error(error, e);
1022 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error);
1024 } catch (QuotaExceededException e) {
1025 if (uploadedFile != null && uploadedFile.exists())
1026 uploadedFile.delete();
1027 String error = "Not enough free space available";
1028 if (logger.isDebugEnabled())
1029 logger.debug(error, e);
1030 response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error);
1032 } catch (ObjectNotFoundException e) {
1033 if (uploadedFile != null && uploadedFile.exists())
1034 uploadedFile.delete();
1035 String error = "A specified object was not found";
1036 logger.error(error, e);
1037 response.sendError(HttpServletResponse.SC_NOT_FOUND, error);
1038 } catch (RpcException e) {
1039 if (uploadedFile != null && uploadedFile.exists())
1040 uploadedFile.delete();
1041 String error = "An error occurred while communicating with the service";
1042 logger.error(error, e);
1043 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1044 } catch (Exception e) {
1045 if (uploadedFile != null && uploadedFile.exists())
1046 uploadedFile.delete();
1047 String error = "An internal server error occurred";
1048 logger.error(error, e);
1049 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
1054 * Move the resource in the specified path to the specified destination.
1056 * @param req the HTTP request
1057 * @param resp the HTTP response
1058 * @param path the path of the resource
1059 * @param moveTo the destination of the move procedure
1060 * @throws IOException if an input/output error occurs
1062 private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException {
1063 final User user = getUser(req);
1064 User owner = getOwner(req);
1065 Object resource = null;
1067 resource = getService().getResourceAtPath(owner.getId(), path, true);
1068 } catch (ObjectNotFoundException e) {
1069 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1071 } catch (RpcException e) {
1072 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1076 String destination = null;
1077 User destOwner = null;
1078 boolean exists = true;
1080 destination = getDestinationPath(req, encodePath(moveTo));
1081 destination = URLDecoder.decode(destination, "UTF-8");
1082 destOwner = getDestinationOwner(req);
1083 getService().getResourceAtPath(destOwner.getId(), destination, true);
1084 } catch (ObjectNotFoundException e) {
1086 } catch (URISyntaxException e) {
1087 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1089 } catch (RpcException e) {
1090 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1094 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1099 final User dOwner = destOwner;
1100 final String dest = destination;
1101 if (resource instanceof FolderDTO) {
1102 final FolderDTO folder = (FolderDTO) resource;
1103 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1105 public Void call() throws Exception {
1106 getService().moveFolderToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1111 final FileHeaderDTO file = (FileHeaderDTO) resource;
1112 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1114 public Void call() throws Exception {
1115 getService().moveFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1121 } catch (InsufficientPermissionsException e) {
1122 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1123 } catch (ObjectNotFoundException e) {
1124 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1125 } catch (RpcException e) {
1126 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1127 } catch (DuplicateNameException e) {
1128 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1129 } catch (GSSIOException e) {
1130 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1131 } catch (QuotaExceededException e) {
1132 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1133 } catch (Exception e) {
1134 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1139 * Copy the resource in the specified path to the specified destination.
1141 * @param req the HTTP request
1142 * @param resp the HTTP response
1143 * @param path the path of the resource
1144 * @param copyTo the destination of the copy procedure
1145 * @throws IOException if an input/output error occurs
1147 private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException {
1148 final User user = getUser(req);
1149 User owner = getOwner(req);
1150 Object resource = null;
1152 resource = getService().getResourceAtPath(owner.getId(), path, true);
1153 } catch (ObjectNotFoundException e) {
1154 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1156 } catch (RpcException e) {
1157 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1161 String destination = null;
1162 User destOwner = null;
1163 boolean exists = true;
1165 String destinationEncoded = getDestinationPath(req, encodePath(copyTo));
1166 destination = URLDecoder.decode(destinationEncoded, "UTF-8");
1167 destOwner = getDestinationOwner(req);
1168 getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true);
1169 } catch (ObjectNotFoundException e) {
1171 } catch (URISyntaxException e) {
1172 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1174 } catch (RpcException e) {
1175 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1179 resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists");
1184 final User dOwner = destOwner;
1185 final String dest = destination;
1186 if (resource instanceof FolderDTO) {
1187 final FolderDTO folder = (FolderDTO) resource;
1188 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1190 public Void call() throws Exception {
1191 getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folder.getId(), dest);
1196 final FileHeaderDTO file = (FileHeaderDTO) resource;
1197 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1199 public Void call() throws Exception {
1200 getService().copyFileToPath(user.getId(), dOwner.getId(), file.getId(), dest);
1205 } catch (InsufficientPermissionsException e) {
1206 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1207 } catch (ObjectNotFoundException e) {
1208 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1209 } catch (RpcException e) {
1210 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1211 } catch (DuplicateNameException e) {
1212 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1213 } catch (GSSIOException e) {
1214 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
1215 } catch (QuotaExceededException e) {
1216 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1217 } catch (Exception e) {
1218 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination);
1222 private String encodePath(String path) throws UnsupportedEncodingException{
1223 StringTokenizer str = new StringTokenizer(path, "/:", true);
1224 String result = new String();
1225 while(str.hasMoreTokens()){
1226 String token = str.nextToken();
1227 if(!token.equals("/") && !token.equals(":"))
1228 token = URLEncoder.encode(token,"UTF-8");
1229 result = result + token;
1234 * A helper method that extracts the relative resource path,
1235 * after removing the 'files' namespace.
1236 * The path returned is <i>not</i> URL-decoded.
1238 * @param req the HTTP request
1239 * @param path the specified path
1240 * @return the path relative to the root folder
1241 * @throws URISyntaxException
1242 * @throws RpcException in case an error occurs while communicating
1244 * @throws UnsupportedEncodingException
1246 private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException {
1247 URI uri = new URI(path);
1248 String dest = uri.getRawPath();
1249 // Remove the context path from the destination URI.
1250 String contextPath = req.getContextPath();
1251 if (!dest.startsWith(contextPath))
1252 throw new URISyntaxException(dest, "Destination path does not start with " + contextPath);
1253 dest = dest.substring(contextPath.length());
1254 // Remove the servlet path from the destination URI.
1255 String servletPath = req.getServletPath();
1256 if (!dest.startsWith(servletPath))
1257 throw new URISyntaxException(dest, "Destination path does not start with " + servletPath);
1258 dest = dest.substring(servletPath.length());
1259 // Strip the username part
1260 if (dest.length() < 2)
1261 throw new URISyntaxException(dest, "No username in the destination URI");
1262 int slash = dest.substring(1).indexOf('/');
1264 throw new URISyntaxException(dest, "No username in the destination URI");
1265 // Decode the user to get the proper characters (mainly the @)
1266 String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8");
1268 o = getService().findUser(owner);
1270 throw new URISyntaxException(dest, "User " + owner + " not found");
1272 req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o);
1273 dest = dest.substring(slash + 1);
1275 // Chop the resource namespace part
1276 dest = dest.substring(RequestHandler.PATH_FILES.length());
1278 dest = dest.endsWith("/")? dest: dest + '/';
1283 * Move the resource in the specified path to the trash bin.
1285 * @param req the HTTP request
1286 * @param resp the HTTP response
1287 * @param path the path of the resource
1288 * @throws IOException if an input/output error occurs
1290 private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1291 final User user = getUser(req);
1292 User owner = getOwner(req);
1293 Object resource = null;
1295 resource = getService().getResourceAtPath(owner.getId(), path, true);
1296 } catch (ObjectNotFoundException e) {
1297 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1299 } catch (RpcException e) {
1300 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1305 if (resource instanceof FolderDTO) {
1306 final FolderDTO folder = (FolderDTO) resource;
1307 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1309 public Void call() throws Exception {
1310 getService().moveFolderToTrash(user.getId(), folder.getId());
1315 final FileHeaderDTO file = (FileHeaderDTO) resource;
1316 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1318 public Void call() throws Exception {
1319 getService().moveFileToTrash(user.getId(), file.getId());
1324 } catch (InsufficientPermissionsException e) {
1325 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1326 } catch (ObjectNotFoundException e) {
1327 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1328 } catch (RpcException e) {
1329 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1330 } catch (Exception e) {
1331 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1336 * Restore the resource in the specified path from the trash bin.
1338 * @param req the HTTP request
1339 * @param resp the HTTP response
1340 * @param path the path of the resource
1341 * @throws IOException if an input/output error occurs
1343 private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1344 final User user = getUser(req);
1345 User owner = getOwner(req);
1346 Object resource = null;
1348 resource = getService().getResourceAtPath(owner.getId(), path, false);
1349 } catch (ObjectNotFoundException e) {
1350 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1352 } catch (RpcException e) {
1353 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1358 if (resource instanceof FolderDTO) {
1359 final FolderDTO folder = (FolderDTO) resource;
1360 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1362 public Void call() throws Exception {
1363 getService().removeFolderFromTrash(user.getId(), folder.getId());
1368 final FileHeaderDTO file = (FileHeaderDTO) resource;
1369 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1371 public Void call() throws Exception {
1372 getService().removeFileFromTrash(user.getId(), file.getId());
1377 } catch (InsufficientPermissionsException e) {
1378 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1379 } catch (ObjectNotFoundException e) {
1380 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1381 } catch (RpcException e) {
1382 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1383 } catch (Exception e) {
1384 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1389 * Update the resource in the specified path.
1391 * @param req the HTTP request
1392 * @param resp the HTTP response
1393 * @param path the path of the resource
1394 * @throws IOException if an input/output error occurs
1396 private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException {
1397 final User user = getUser(req);
1398 User owner = getOwner(req);
1399 Object resource = null;
1402 resource = getService().getResourceAtPath(owner.getId(), path, false);
1403 } catch (ObjectNotFoundException e) {
1404 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
1406 } catch (RpcException e) {
1407 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1410 StringBuffer input = new StringBuffer();
1411 JSONObject json = null;
1412 if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded"))
1413 input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER));
1415 // Assume application/json
1416 BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
1418 while ((line = reader.readLine()) != null)
1423 json = new JSONObject(input.toString());
1424 if (logger.isDebugEnabled())
1425 logger.debug("JSON update: " + json);
1426 if (resource instanceof FolderDTO) {
1427 final FolderDTO folder = (FolderDTO) resource;
1428 String name = json.optString("name");
1429 if (!isValidResourceName(name)) {
1430 resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
1433 JSONArray permissions = json.optJSONArray("permissions");
1434 Set<PermissionDTO> perms = null;
1435 if (permissions != null)
1436 perms = parsePermissions(user, permissions);
1437 Boolean readForAll = null;
1438 if (json.opt("readForAll") != null)
1439 readForAll = json.optBoolean("readForAll");
1440 if (!name.isEmpty() || permissions != null || readForAll != null) {
1441 final String fName = name.isEmpty()? null: name;
1442 final Boolean freadForAll = readForAll;
1443 final Set<PermissionDTO> fPerms = perms;
1444 FolderDTO folderUpdated = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1446 public FolderDTO call() throws Exception {
1447 return getService().updateFolder(user.getId(), folder.getId(), fName, freadForAll, fPerms);
1451 resp.getWriter().println(getNewUrl(req, folderUpdated));
1454 final FileHeaderDTO file = (FileHeaderDTO) resource;
1456 if (json.opt("name") != null)
1457 name = json.optString("name");
1458 Long modificationDate = null;
1459 if (json.optLong("modificationDate") != 0)
1460 modificationDate = json.optLong("modificationDate");
1461 Boolean versioned = null;
1462 if (json.opt("versioned") != null)
1463 versioned = json.getBoolean("versioned");
1464 JSONArray tagset = json.optJSONArray("tags");
1466 StringBuffer t = new StringBuffer();
1467 if (tagset != null) {
1468 for (int i = 0; i < tagset.length(); i++)
1469 t.append(tagset.getString(i) + ',');
1470 tags = t.toString();
1472 JSONArray permissions = json.optJSONArray("permissions");
1473 Set<PermissionDTO> perms = null;
1474 if (permissions != null)
1475 perms = parsePermissions(user, permissions);
1476 Boolean readForAll = null;
1477 if (json.opt("readForAll") != null)
1478 readForAll = json.optBoolean("readForAll");
1479 if (name != null || tags != null || modificationDate != null
1480 || versioned != null || perms != null
1481 || readForAll != null) {
1482 final String fName = name;
1483 final String fTags = tags;
1484 final Date mDate = modificationDate != null? new Date(modificationDate): null;
1485 final Boolean fVersioned = versioned;
1486 final Boolean fReadForAll = readForAll;
1487 final Set<PermissionDTO> fPerms = perms;
1488 new TransactionHelper<Object>().tryExecute(new Callable<Object>() {
1490 public Object call() throws Exception {
1491 getService().updateFile(user.getId(), file.getId(),
1492 fName, fTags, mDate, fVersioned,
1493 fReadForAll, fPerms);
1500 } catch (JSONException e) {
1501 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1502 } catch (InsufficientPermissionsException e) {
1503 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1504 } catch (ObjectNotFoundException e) {
1505 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
1506 } catch (DuplicateNameException e) {
1507 resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage());
1508 } catch (RpcException e) {
1509 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1510 } catch (Exception e) {
1511 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1517 * Returns the new URL of an updated folder.
1519 private String getNewUrl(HttpServletRequest req, FolderDTO folder) throws UnsupportedEncodingException {
1520 String parentUrl = URLDecoder.decode(getContextPath(req, true),"UTF-8");
1521 String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8");
1522 if (parentUrl.indexOf(fpath) != -1)
1523 parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath));
1524 if(!parentUrl.endsWith("/"))
1525 parentUrl = parentUrl+"/";
1526 parentUrl = parentUrl+folder.getOwner().getUsername()+PATH_FILES+folder.getPath();
1531 * Helper method to convert a JSON array of permissions into a set of
1532 * PermissionDTO objects.
1534 * @param user the current user
1535 * @param permissions the JSON array to parse
1536 * @return the parsed set of permissions
1537 * @throws JSONException if there was an error parsing the JSON object
1538 * @throws RpcException if there was an error communicating with the EJB
1539 * @throws ObjectNotFoundException if the user could not be found
1540 * @throws UnsupportedEncodingException
1542 private Set<PermissionDTO> parsePermissions(User user, JSONArray permissions)
1543 throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException {
1544 if (permissions == null)
1546 Set<PermissionDTO> perms = new HashSet<PermissionDTO>();
1547 for (int i = 0; i < permissions.length(); i++) {
1548 JSONObject j = permissions.getJSONObject(i);
1549 PermissionDTO perm = new PermissionDTO();
1550 perm.setModifyACL(j.optBoolean("modifyACL"));
1551 perm.setRead(j.optBoolean("read"));
1552 perm.setWrite(j.optBoolean("write"));
1553 String permUser = j.optString("user");
1554 if (!permUser.isEmpty()) {
1555 User u = getService().findUser(permUser);
1557 throw new ObjectNotFoundException("User " + permUser + " not found");
1558 perm.setUser(u.getDTO());
1560 // 31/8/2009: Add optional groupUri which takes priority if it exists
1561 String permGroupUri = j.optString("groupUri");
1562 String permGroup = j.optString("group");
1563 if (!permGroupUri.isEmpty()) {
1564 String[] names = permGroupUri.split("/");
1565 String grp = URLDecoder.decode(names[names.length - 1], "UTF-8");
1566 String usr = URLDecoder.decode(names[names.length - 3], "UTF-8");
1567 User u = getService().findUser(usr);
1569 throw new ObjectNotFoundException("User " + permUser + " not found");
1570 GroupDTO g = getService().getGroup(u.getId(), grp);
1573 else if (!permGroup.isEmpty()) {
1574 GroupDTO g = getService().getGroup(user.getId(), permGroup);
1577 if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty())
1578 throw new JSONException("A permission must correspond to either a user or a group");
1585 * Creates a new folder with the specified name under the folder in the provided path.
1587 * @param req the HTTP request
1588 * @param resp the HTTP response
1589 * @param path the parent folder path
1590 * @param folderName the name of the new folder
1591 * @throws IOException if an input/output error occurs
1593 private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException {
1594 if (logger.isDebugEnabled())
1595 logger.debug("Creating folder " + folderName + " in '" + path);
1597 final User user = getUser(req);
1598 User owner = getOwner(req);
1599 boolean exists = true;
1601 getService().getResourceAtPath(owner.getId(), path + folderName, false);
1602 } catch (ObjectNotFoundException e) {
1604 } catch (RpcException e) {
1605 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1610 resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE +
1611 ", " + METHOD_HEAD);
1612 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1618 parent = getService().getResourceAtPath(owner.getId(), path, true);
1619 } catch (ObjectNotFoundException e) {
1620 resp.sendError(HttpServletResponse.SC_CONFLICT);
1622 } catch (RpcException e) {
1623 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1627 if (parent instanceof FolderDTO) {
1628 final FolderDTO folder = (FolderDTO) parent;
1629 FolderDTO newFolder = new TransactionHelper<FolderDTO>().tryExecute(new Callable<FolderDTO>() {
1631 public FolderDTO call() throws Exception {
1632 return getService().createFolder(user.getId(), folder.getId(), folderName);
1636 String newResource = getApiRoot() + newFolder.getURI();
1637 resp.setHeader("Location", newResource);
1638 resp.setContentType("text/plain");
1639 PrintWriter out = resp.getWriter();
1640 out.println(newResource);
1642 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1645 } catch (DuplicateNameException e) {
1646 resp.sendError(HttpServletResponse.SC_CONFLICT);
1648 } catch (InsufficientPermissionsException e) {
1649 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1651 } catch (ObjectNotFoundException e) {
1652 resp.sendError(HttpServletResponse.SC_CONFLICT);
1654 } catch (RpcException e) {
1655 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName);
1657 } catch (Exception e) {
1658 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1661 resp.setStatus(HttpServletResponse.SC_CREATED);
1667 * @throws IOException
1668 * @throws FileNotFoundException
1670 void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException {
1671 String path = getInnerPath(req, PATH_FILES);
1673 path = URLDecoder.decode(path, "UTF-8");
1674 } catch (IllegalArgumentException e) {
1675 resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
1678 if (logger.isDebugEnabled())
1679 logger.debug("Updating resource: " + path);
1681 final User user = getUser(req);
1682 User owner = getOwner(req);
1683 boolean exists = true;
1684 Object resource = null;
1685 FileHeaderDTO file = null;
1687 resource = getService().getResourceAtPath(owner.getId(), path, false);
1688 } catch (ObjectNotFoundException e) {
1690 } catch (RpcException e) {
1691 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1696 if (resource instanceof FileHeaderDTO)
1697 file = (FileHeaderDTO) resource;
1699 resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder");
1702 boolean result = true;
1704 // Temporary content file used to support partial PUT.
1705 File contentFile = null;
1707 Range range = parseContentRange(req, resp);
1709 InputStream resourceInputStream = null;
1711 // Append data specified in ranges to existing content for this
1712 // resource - create a temporary file on the local filesystem to
1713 // perform this operation.
1714 // Assume just one range is specified for now
1715 if (range != null) {
1717 contentFile = executePartialPut(req, range, path);
1718 } catch (RpcException e) {
1719 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1721 } catch (ObjectNotFoundException e) {
1722 resp.sendError(HttpServletResponse.SC_CONFLICT);
1724 } catch (InsufficientPermissionsException e) {
1725 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1728 resourceInputStream = new FileInputStream(contentFile);
1730 resourceInputStream = req.getInputStream();
1733 FolderDTO folder = null;
1734 Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true);
1735 if (!(parent instanceof FolderDTO)) {
1736 resp.sendError(HttpServletResponse.SC_CONFLICT);
1739 folder = (FolderDTO) parent;
1740 final String name = getLastElement(path);
1741 final String mimeType = context.getMimeType(name);
1742 File uploadedFile = null;
1744 uploadedFile = getService().uploadFile(resourceInputStream, user.getId());
1745 } catch (IOException ex) {
1746 throw new GSSIOException(ex, false);
1748 FileHeaderDTO fileDTO = null;
1749 final File uploadedf = uploadedFile;
1750 final FolderDTO parentf = folder;
1751 final FileHeaderDTO f = file;
1753 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1755 public FileHeaderDTO call() throws Exception {
1756 return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1760 fileDTO = new TransactionHelper<FileHeaderDTO>().tryExecute(new Callable<FileHeaderDTO>() {
1762 public FileHeaderDTO call() throws Exception {
1763 return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath());
1767 updateAccounting(owner, new Date(), fileDTO.getFileSize());
1768 getService().removeFileUploadProgress(user.getId(), fileDTO.getName());
1769 } catch(ObjectNotFoundException e) {
1771 } catch (RpcException e) {
1772 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1774 } catch (IOException e) {
1775 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1777 } catch (GSSIOException e) {
1778 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1780 } catch (DuplicateNameException e) {
1781 resp.sendError(HttpServletResponse.SC_CONFLICT);
1783 } catch (InsufficientPermissionsException e) {
1784 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1786 } catch (QuotaExceededException e) {
1787 resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage());
1789 } catch (Exception e) {
1790 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path);
1796 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1798 resp.setStatus(HttpServletResponse.SC_CREATED);
1800 resp.sendError(HttpServletResponse.SC_CONFLICT);
1804 * Delete a resource.
1806 * @param req The servlet request we are processing
1807 * @param resp The servlet response we are processing
1808 * @throws IOException if the response cannot be sent
1810 void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException {
1811 String path = getInnerPath(req, PATH_FILES);
1812 if (logger.isDebugEnabled())
1813 logger.debug("Deleting resource '" + path);
1814 path = URLDecoder.decode(path, "UTF-8");
1815 final User user = getUser(req);
1816 User owner = getOwner(req);
1817 boolean exists = true;
1818 Object object = null;
1820 object = getService().getResourceAtPath(owner.getId(), path, false);
1821 } catch (ObjectNotFoundException e) {
1823 } catch (RpcException e) {
1824 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1829 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1833 FolderDTO folder = null;
1834 FileHeaderDTO file = null;
1835 if (object instanceof FolderDTO)
1836 folder = (FolderDTO) object;
1838 file = (FileHeaderDTO) object;
1842 final FileHeaderDTO f = file;
1843 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1845 public Void call() throws Exception {
1846 getService().deleteFile(user.getId(), f.getId());
1850 } catch (InsufficientPermissionsException e) {
1851 resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1853 } catch (ObjectNotFoundException e) {
1854 // Although we had already found the object, it was
1855 // probably deleted from another thread.
1856 resp.sendError(HttpServletResponse.SC_NOT_FOUND);
1858 } catch (RpcException e) {
1859 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1861 } catch (Exception e) {
1862 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1865 else if (folder != null)
1867 final FolderDTO fo = folder;
1868 new TransactionHelper<Void>().tryExecute(new Callable<Void>() {
1870 public Void call() throws Exception {
1871 getService().deleteFolder(user.getId(), fo.getId());
1875 } catch (InsufficientPermissionsException e) {
1876 resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
1878 } catch (ObjectNotFoundException e) {
1879 resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
1881 } catch (RpcException e) {
1882 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1884 } catch (Exception e) {
1885 resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1888 resp.setStatus(HttpServletResponse.SC_NO_CONTENT);
1893 * Return an InputStream to a JSON representation of the contents
1894 * of this directory.
1896 * @param user the user that made the request
1897 * @param folder the specified directory
1898 * @return an input stream with the rendered contents
1899 * @throws IOException if the response cannot be sent
1900 * @throws ServletException
1901 * @throws InsufficientPermissionsException if the user does not have
1902 * the necessary privileges to read the directory
1904 private InputStream renderJson(User user, FolderDTO folder) throws IOException,
1905 ServletException, InsufficientPermissionsException {
1906 JSONObject json = new JSONObject();
1908 json.put("name", folder.getName()).
1909 put("owner", folder.getOwner().getUsername()).
1910 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1911 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1912 put("deleted", folder.isDeleted()).
1913 put("readForAll", folder.isReadForAll());
1915 if (folder.getAuditInfo().getModifiedBy() != null)
1916 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
1917 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
1918 if (folder.getParent() != null) {
1919 JSONObject j = new JSONObject();
1920 j.put("uri", getApiRoot() + folder.getParent().getURI());
1921 j.put("name", folder.getParent().getName());
1922 json.put("parent", j);
1924 List<JSONObject> subfolders = new ArrayList<JSONObject>();
1925 for (FolderDTO f: folder.getSubfolders())
1926 if (!f.isDeleted()) {
1927 JSONObject j = new JSONObject();
1928 j.put("name", f.getName()).
1929 put("uri", getApiRoot() + f.getURI());
1932 json.put("folders", subfolders);
1933 List<JSONObject> files = new ArrayList<JSONObject>();
1934 List<FileHeaderDTO> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false);
1935 for (FileHeaderDTO f: fileHeaders) {
1936 JSONObject j = new JSONObject();
1937 j.put("name", f.getName()).
1938 put("owner", f.getOwner().getUsername()).
1939 put("deleted", f.isDeleted()).
1940 put("version", f.getVersion()).
1941 put("content", f.getMimeType()).
1942 put("size", f.getFileSize()).
1943 put("creationDate", f.getAuditInfo().getCreationDate().getTime()).
1944 put("path", f.getFolder().getPath()).
1945 put("uri", getApiRoot() + f.getURI());
1946 if (f.getAuditInfo().getModificationDate() != null)
1947 j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime());
1950 json.put("files", files);
1951 Set<PermissionDTO> perms = getService().getFolderPermissions(user.getId(), folder.getId());
1952 json.put("permissions", renderJson(perms));
1953 } catch (JSONException e) {
1954 throw new ServletException(e);
1955 } catch (ObjectNotFoundException e) {
1956 throw new ServletException(e);
1957 } catch (RpcException e) {
1958 throw new ServletException(e);
1961 // Prepare a writer to a buffered area
1962 ByteArrayOutputStream stream = new ByteArrayOutputStream();
1963 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
1964 PrintWriter writer = new PrintWriter(osWriter);
1966 // Return an input stream to the underlying bytes
1967 writer.write(json.toString());
1969 return new ByteArrayInputStream(stream.toByteArray());
1973 * Return a String with a JSON representation of the metadata
1974 * of the specified folder.
1975 * @throws RpcException
1976 * @throws InsufficientPermissionsException
1977 * @throws ObjectNotFoundException
1979 private String renderJsonMetadata(User user, FolderDTO folder)
1980 throws ServletException, InsufficientPermissionsException {
1981 // Check if the user has read permission.
1983 if (!getService().canReadFolder(user.getId(), folder.getId()))
1984 throw new InsufficientPermissionsException();
1985 } catch (ObjectNotFoundException e) {
1986 throw new ServletException(e);
1987 } catch (RpcException e) {
1988 throw new ServletException(e);
1991 JSONObject json = new JSONObject();
1993 json.put("name", folder.getName()).
1994 put("owner", folder.getOwner().getUsername()).
1995 put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()).
1996 put("creationDate", folder.getAuditInfo().getCreationDate().getTime()).
1997 put("deleted", folder.isDeleted());
1998 if (folder.getAuditInfo().getModifiedBy() != null)
1999 json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).
2000 put("modificationDate", folder.getAuditInfo().getModificationDate().getTime());
2001 } catch (JSONException e) {
2002 throw new ServletException(e);
2004 return json.toString();
2008 * Return a String with a JSON representation of the metadata
2009 * of the specified file. If an old file body is provided, then
2010 * the metadata of that particular version will be returned.
2012 * @param user the user that made the request
2013 * @param file the specified file header
2014 * @param oldBody the version number
2015 * @return the JSON-encoded file
2016 * @throws ServletException
2017 * @throws InsufficientPermissionsException if the user does not have
2018 * the necessary privileges to read the directory
2020 private String renderJson(User user, FileHeaderDTO file, FileBodyDTO oldBody)
2021 throws ServletException, InsufficientPermissionsException {
2022 JSONObject json = new JSONObject();
2024 // Need to encode file name in order to properly display it in the web client.
2025 json.put("name", URLEncoder.encode(file.getName(),"UTF-8")).
2026 put("owner", file.getOwner().getUsername()).
2027 put("versioned", file.isVersioned()).
2028 put("version", oldBody != null ? oldBody.getVersion() : file.getVersion()).
2029 put("readForAll", file.isReadForAll()).
2030 put("tags", renderJson(file.getTags())).
2031 put("path", file.getFolder().getPath()).
2032 put("uri", getApiRoot() + file.getURI()).
2033 put("deleted", file.isDeleted());
2034 JSONObject j = new JSONObject();
2035 j.put("uri", getApiRoot() + file.getFolder().getURI()).
2036 put("name", URLEncoder.encode(file.getFolder().getName(),"UTF-8"));
2037 json.put("folder", j);
2038 if (oldBody != null)
2039 json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()).
2040 put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()).
2041 put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()).
2042 put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()).
2043 put("content", oldBody.getMimeType()).
2044 put("size", oldBody.getFileSize());
2046 json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()).
2047 put("creationDate", file.getAuditInfo().getCreationDate().getTime()).
2048 put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()).
2049 put("modificationDate", file.getAuditInfo().getModificationDate().getTime()).
2050 put("content", file.getMimeType()).
2051 put("size", file.getFileSize());
2052 Set<PermissionDTO> perms = getService().getFilePermissions(user.getId(), file.getId());
2053 json.put("permissions", renderJson(perms));
2054 } catch (JSONException e) {
2055 throw new ServletException(e);
2056 } catch (ObjectNotFoundException e) {
2057 throw new ServletException(e);
2058 } catch (RpcException e) {
2059 throw new ServletException(e);
2060 } catch (UnsupportedEncodingException e) {
2061 throw new ServletException(e);
2064 return json.toString();
2068 * Return a String with a JSON representation of the
2069 * specified set of permissions.
2071 * @param permissions the set of permissions
2072 * @return the JSON-encoded object
2073 * @throws JSONException
2074 * @throws UnsupportedEncodingException
2076 private JSONArray renderJson(Set<PermissionDTO> permissions) throws JSONException, UnsupportedEncodingException {
2077 JSONArray perms = new JSONArray();
2078 for (PermissionDTO p: permissions) {
2079 JSONObject permission = new JSONObject();
2080 permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL());
2081 if (p.getUser() != null)
2082 permission.put("user", p.getUser().getUsername());
2083 if (p.getGroup() != null) {
2084 GroupDTO group = p.getGroup();
2085 permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(),"UTF-8"));
2086 permission.put("group", URLEncoder.encode(p.getGroup().getName(),"UTF-8"));
2088 perms.put(permission);
2094 * Return a String with a JSON representation of the
2095 * specified collection of tags.
2097 * @param tags the collection of tags
2098 * @return the JSON-encoded object
2099 * @throws JSONException
2100 * @throws UnsupportedEncodingException
2102 private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException {
2103 JSONArray tagArray = new JSONArray();
2104 for (String t: tags)
2105 tagArray.put(URLEncoder.encode(t,"UTF-8"));
2110 * Retrieves the user who owns the destination namespace, for a
2111 * copy or move request.
2113 * @param req the HTTP request
2114 * @return the owner of the namespace
2116 protected User getDestinationOwner(HttpServletRequest req) {
2117 return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE);
2121 * A helper inner class for updating the progress status of a file upload.
2125 public static class StatusProgressListener implements ProgressListener {
2126 private int percentLogged = 0;
2127 private long bytesTransferred = 0;
2129 private long fileSize = -100;
2131 private Long userId;
2133 private String filename;
2135 private ExternalAPI service;
2137 public StatusProgressListener(ExternalAPI aService) {
2142 * Modify the userId.
2144 * @param aUserId the userId to set
2146 public void setUserId(Long aUserId) {
2151 * Modify the filename.
2153 * @param aFilename the filename to set
2155 public void setFilename(String aFilename) {
2156 filename = aFilename;
2160 public void update(long bytesRead, long contentLength, int items) {
2161 //monitoring per percent of bytes uploaded
2162 bytesTransferred = bytesRead;
2163 if (fileSize != contentLength)
2164 fileSize = contentLength;
2165 int percent = new Long(bytesTransferred * 100 / fileSize).intValue();
2167 if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0 )
2168 if (percent != percentLogged){
2169 percentLogged = percent;
2171 if (userId != null && filename != null)
2172 service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize);
2173 } catch (ObjectNotFoundException e) {
2174 // Swallow the exception since it is going to be caught
2175 // by previously called methods
2182 * Return an InputStream to an HTML representation of the contents of this
2185 * @param contextPath Context path to which our internal paths are relative
2186 * @param path the requested path to the resource
2187 * @param folder the specified directory
2188 * @param user the specified user
2189 * @return an input stream with the rendered contents
2190 * @throws IOException
2191 * @throws ServletException
2193 private InputStream renderHtml(String contextPath, String path, FolderDTO folder, User user)
2194 throws IOException, ServletException {
2195 String name = folder.getName();
2196 // Prepare a writer to a buffered area
2197 ByteArrayOutputStream stream = new ByteArrayOutputStream();
2198 OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
2199 PrintWriter writer = new PrintWriter(osWriter);
2200 StringBuffer sb = new StringBuffer();
2201 // rewriteUrl(contextPath) is expensive. cache result for later reuse
2202 String rewrittenContextPath = rewriteUrl(contextPath);
2203 // Render the page header
2204 sb.append("<html>\r\n");
2205 sb.append("<head>\r\n");
2206 sb.append("<title>");
2207 sb.append("Index of " + name);
2208 sb.append("</title>\r\n");
2209 sb.append("<STYLE><!--");
2211 sb.append("--></STYLE> ");
2212 sb.append("</head>\r\n");
2213 sb.append("<body>");
2215 sb.append("Index of " + name);
2217 // Render the link to our parent (if required)
2218 String parentDirectory = path;
2219 if (parentDirectory.endsWith("/"))
2220 parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1);
2221 int slash = parentDirectory.lastIndexOf('/');
2223 String parent = path.substring(0, slash);
2224 sb.append(" - <a href=\"");
2225 sb.append(rewrittenContextPath);
2226 if (parent.equals(""))
2229 if (!parent.endsWith("/"))
2233 sb.append("Up To " + parent);
2239 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2241 sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n");
2243 // Render the column headings
2244 sb.append("<tr>\r\n");
2245 sb.append("<td align=\"left\"><font size=\"+1\"><strong>");
2247 sb.append("</strong></font></td>\r\n");
2248 sb.append("<td align=\"center\"><font size=\"+1\"><strong>");
2250 sb.append("</strong></font></td>\r\n");
2251 sb.append("<td align=\"right\"><font size=\"+1\"><strong>");
2252 sb.append("Last modified");
2253 sb.append("</strong></font></td>\r\n");
2255 // Render the directory entries within this directory
2256 boolean shade = false;
2257 Iterator iter = folder.getSubfolders().iterator();
2258 while (iter.hasNext()) {
2259 FolderDTO subf = (FolderDTO) iter.next();
2260 if(subf.isReadForAll() && !subf.isDeleted()){
2261 String resourceName = subf.getName();
2262 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2267 sb.append(" bgcolor=\"#eeeeee\"");
2271 sb.append("<td align=\"left\"> \r\n");
2272 sb.append("<a href=\"");
2273 sb.append(rewrittenContextPath+path);
2274 sb.append(rewriteUrl(resourceName));
2276 sb.append("\"><tt>");
2277 sb.append(RequestUtil.filter(resourceName));
2279 sb.append("</tt></a></td>\r\n");
2281 sb.append("<td align=\"right\"><tt>");
2282 sb.append(" ");
2283 sb.append("</tt></td>\r\n");
2285 sb.append("<td align=\"right\"><tt>");
2286 sb.append(getLastModifiedHttp(folder.getAuditInfo()));
2287 sb.append("</tt></td>\r\n");
2289 sb.append("</tr>\r\n");
2293 List<FileHeaderDTO> files;
2295 files = getService().getFiles(user.getId(), folder.getId(), true);
2296 } catch (ObjectNotFoundException e) {
2297 throw new ServletException(e.getMessage());
2298 } catch (InsufficientPermissionsException e) {
2299 throw new ServletException(e.getMessage());
2300 } catch (RpcException e) {
2301 throw new ServletException(e.getMessage());
2303 for (FileHeaderDTO file : files)
2304 //Display only file resources that are marked as public and are not deleted
2305 if(file.isReadForAll() && !file.isDeleted()){
2306 String resourceName = file.getName();
2307 if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF"))
2312 sb.append(" bgcolor=\"#eeeeee\"");
2316 sb.append("<td align=\"left\"> \r\n");
2317 sb.append("<a href=\"");
2318 sb.append(rewrittenContextPath + path);
2319 sb.append(rewriteUrl(resourceName));
2320 sb.append("\"><tt>");
2321 sb.append(RequestUtil.filter(resourceName));
2322 sb.append("</tt></a></td>\r\n");
2324 sb.append("<td align=\"right\"><tt>");
2325 sb.append(renderSize(file.getFileSize()));
2326 sb.append("</tt></td>\r\n");
2328 sb.append("<td align=\"right\"><tt>");
2329 sb.append(getLastModifiedHttp(file.getAuditInfo()));
2330 sb.append("</tt></td>\r\n");
2332 sb.append("</tr>\r\n");
2335 // Render the page footer
2336 sb.append("</table>\r\n");
2338 sb.append("<HR size=\"1\" noshade=\"noshade\">");
2339 sb.append("</body>\r\n");
2340 sb.append("</html>\r\n");
2342 // Return an input stream to the underlying bytes
2343 writer.write(sb.toString());
2345 return new ByteArrayInputStream(stream.toByteArray());