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