Java tutorial
/* * Copyright 2008, 2009, 2010 Electronic Business Systems Ltd. * * This file is part of GSS. * * GSS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GSS is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GSS. If not, see <http://www.gnu.org/licenses/>. */ package org.gss_project.gss.server.rest; import static org.gss_project.gss.server.configuration.GSSConfigurationFactory.getConfiguration; import org.gss_project.gss.common.exceptions.DuplicateNameException; import org.gss_project.gss.common.exceptions.GSSIOException; import org.gss_project.gss.common.exceptions.InsufficientPermissionsException; import org.gss_project.gss.common.exceptions.ObjectNotFoundException; import org.gss_project.gss.common.exceptions.QuotaExceededException; import org.gss_project.gss.common.exceptions.RpcException; import org.gss_project.gss.server.Login; import org.gss_project.gss.server.domain.FileBody; import org.gss_project.gss.server.domain.FileHeader; import org.gss_project.gss.server.domain.FileUploadStatus; import org.gss_project.gss.server.domain.Folder; import org.gss_project.gss.server.domain.Group; import org.gss_project.gss.server.domain.Permission; import org.gss_project.gss.server.domain.User; import org.gss_project.gss.server.ejb.ExternalAPI; import org.gss_project.gss.server.ejb.TransactionHelper; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.Callable; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.ProgressListener; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.fileupload.util.Streams; import org.apache.commons.httpclient.util.DateParseException; import org.apache.commons.httpclient.util.DateUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * A class that handles operations on the 'files' namespace. * * @author past */ public class FilesHandler extends RequestHandler { /** * The request parameter name for fetching a different version. */ private static final String VERSION_PARAM = "version"; /** * The request attribute containing the owner of the destination URI * in a copy or move request. */ private static final String DESTINATION_OWNER_ATTRIBUTE = "destOwner"; private static final int TRACK_PROGRESS_PERCENT = 5; /** * The form parameter name that contains the signature in a browser POST upload. */ private static final String AUTHORIZATION_PARAMETER = "Authorization"; /** * The form parameter name that contains the date in a browser POST upload. */ private static final String DATE_PARAMETER = "Date"; /** * The request parameter name for making an upload progress request. */ private static final String PROGRESS_PARAMETER = "progress"; /** * The request parameter name for restoring a previous version of a file. */ private static final String RESTORE_VERSION_PARAMETER = "restoreVersion"; /** * The logger. */ private static Log logger = LogFactory.getLog(FilesHandler.class); /** * The servlet context provided by the call site. */ private ServletContext context; /** * The style sheet for displaying the directory listings. */ 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;}"; /** * @param servletContext */ public FilesHandler(ServletContext servletContext) { context = servletContext; } private void updateAccounting(final User user, final Date date, final long bandwidthDiff) { try { new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().updateAccounting(user, date, bandwidthDiff); return null; } }); } catch (RuntimeException e) { throw e; } catch (Exception e) { // updateAccounting() doesn't throw any checked exceptions assert false; } } /** * Serve the specified resource, optionally including the data content. * * @param req The servlet request we are processing * @param resp The servlet response we are creating * @param content Should the content be included? * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs * @throws RpcException * @throws InsufficientPermissionsException * @throws ObjectNotFoundException */ @Override protected void serveResource(HttpServletRequest req, HttpServletResponse resp, boolean content) throws IOException, ServletException { boolean authDeferred = getAuthDeferred(req); String path = getInnerPath(req, PATH_FILES); if (path.equals("")) path = "/"; try { path = URLDecoder.decode(path, "UTF-8"); } catch (IllegalArgumentException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); return; } String progress = req.getParameter(PROGRESS_PARAMETER); if (logger.isDebugEnabled()) if (content) logger.debug("Serving resource '" + path + "' headers and data"); else logger.debug("Serving resource '" + path + "' headers only"); User user = getUser(req); User owner = getOwner(req); boolean exists = true; Object resource = null; FileHeader file = null; Folder folder = null; try { resource = getService().getResourceAtPath(owner.getId(), path, false); } catch (ObjectNotFoundException e) { exists = false; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (!exists && authDeferred) { // We do not want to leak information if the request // was not authenticated. resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } if (resource instanceof Folder) folder = (Folder) resource; else file = (FileHeader) resource; // Note that file will be null, if (!exists). // Now it's time to perform the deferred authentication check. // Since regular signature checking was already performed, // we need to check the read-all flag or the signature-in-parameters. if (authDeferred) { if (file != null && !file.isReadForAll() && content) { // Check for GET with the signature in the request parameters. String auth = req.getParameter(AUTHORIZATION_PARAMETER); String dateParam = req.getParameter(DATE_PARAMETER); if (auth == null || dateParam == null) { // Check for a valid authentication cookie. if (req.getCookies() != null) { boolean found = false; for (Cookie cookie : req.getCookies()) if (Login.AUTH_COOKIE.equals(cookie.getName())) { String cookieauth = cookie.getValue(); int sepIndex = cookieauth.indexOf(Login.COOKIE_SEPARATOR); if (sepIndex == -1) { handleAuthFailure(req, resp); return; } String username = URLDecoder.decode(cookieauth.substring(0, sepIndex), "US-ASCII"); user = null; try { user = getService().findUser(username); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (user == null) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } req.setAttribute(USER_ATTRIBUTE, user); String token = cookieauth.substring(sepIndex + 1); if (user.getAuthToken() == null) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } if (!Arrays.equals(user.getAuthToken(), Base64.decodeBase64(token))) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } found = true; break; } if (!found) { handleAuthFailure(req, resp); return; } } else { handleAuthFailure(req, resp); return; } } else { long timestamp; try { timestamp = DateUtil.parseDate(dateParam).getTime(); } catch (DateParseException e) { resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); return; } // Fetch the Authorization parameter and find the user specified in it. String[] authParts = auth.split(" "); if (authParts.length != 2) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String username = authParts[0]; String signature = authParts[1]; user = null; try { user = getService().findUser(username); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (user == null) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } req.setAttribute(USER_ATTRIBUTE, user); // Remove the servlet path from the request URI. String p = req.getRequestURI(); String servletPath = req.getContextPath() + req.getServletPath(); p = p.substring(servletPath.length()); // Validate the signature in the Authorization parameter. String data = req.getMethod() + dateParam + p; if (!isSignatureValid(signature, user, data)) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } } } else if (folder != null && folder.isReadForAll() || file != null && file.isReadForAll()) { //This case refers to a folder or file with public privileges //For a read-for-all folder request, pretend the owner is making it. user = owner; req.setAttribute(USER_ATTRIBUTE, user); } else if (folder != null && !folder.isReadForAll()) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } else { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } } // If the resource is not a collection, and the resource path // ends with "/" or "\", return NOT FOUND. if (folder == null) if (path.endsWith("/") || path.endsWith("\\")) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI()); return; } // Workaround for IE's broken caching behavior. if (folder != null) resp.setHeader("Expires", "-1"); // A request for upload progress. if (progress != null && content) { serveProgress(req, resp, progress, user, file); return; } // Fetch the version to retrieve, if specified. String verStr = req.getParameter(VERSION_PARAM); int version = 0; FileBody oldBody = null; if (verStr != null && file != null) try { version = Integer.valueOf(verStr); } catch (NumberFormatException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, req.getRequestURI()); return; } if (version > 0) try { oldBody = getService().getFileVersion(user.getId(), file.getId(), version); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } // Check if the conditions specified in the optional If headers are // satisfied. Doing this for folders would require recursive checking // for all of their children, which in turn would defy the purpose of // the optimization. if (folder == null) { // Checking If headers. if (!checkIfHeaders(req, resp, file, oldBody)) return; } else if (!checkIfModifiedSince(req, resp, folder)) return; // Find content type. String contentType = null; boolean isContentHtml = false; boolean expectJSON = false; if (file != null) { contentType = version > 0 ? oldBody.getMimeType() : file.getCurrentBody().getMimeType(); if (contentType == null) { contentType = context.getMimeType(file.getName()); file.getCurrentBody().setMimeType(contentType); } } else { // folder != null String accept = req.getHeader("Accept"); // The order in this conditional pessimizes the common API case, // but is important for backwards compatibility with existing // clients who send no accept header and expect a JSON response. if (accept != null && accept.contains("text/html")) { contentType = "text/html;charset=UTF-8"; isContentHtml = true; //this is the case when clients send the appropriate headers, the contentType is "text/html" //and expect a JSON response. The above check applies to FireGSS client expectJSON = !authDeferred ? true : false; } else if (authDeferred && req.getMethod().equals(METHOD_GET)) { contentType = "text/html;charset=UTF-8"; isContentHtml = true; expectJSON = false; } else { contentType = "application/json;charset=UTF-8"; expectJSON = true; } } ArrayList ranges = null; long contentLength = -1L; if (file != null) { // Parse range specifier. ranges = parseRange(req, resp, file, oldBody); // ETag header resp.setHeader("ETag", getETag(file, oldBody)); // Last-Modified header. String lastModified = oldBody == null ? getLastModifiedHttp(file.getAuditInfo()) : getLastModifiedHttp(oldBody.getAuditInfo()); resp.setHeader("Last-Modified", lastModified); // X-GSS-Metadata header. try { resp.setHeader("X-GSS-Metadata", renderJson(user, file, oldBody)); } catch (InsufficientPermissionsException e) { resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } // Get content length. contentLength = version > 0 ? oldBody.getFileSize() : file.getCurrentBody().getFileSize(); // Special case for zero length files, which would cause a // (silent) ISE when setting the output buffer size. if (contentLength == 0L) content = false; } else // Set the folder X-GSS-Metadata header. try { resp.setHeader("X-GSS-Metadata", renderJsonMetadata(user, folder)); } catch (InsufficientPermissionsException e) { resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } ServletOutputStream ostream = null; PrintWriter writer = null; if (content) try { ostream = resp.getOutputStream(); } catch (IllegalStateException e) { // If it fails, we try to get a Writer instead if we're // trying to serve a text file if (contentType == null || contentType.startsWith("text") || contentType.endsWith("xml")) writer = resp.getWriter(); else throw e; } if (folder != null || (ranges == null || ranges.isEmpty()) && req.getHeader("Range") == null || ranges == FULL) { // Set the appropriate output headers if (contentType != null) { if (logger.isDebugEnabled()) logger.debug("contentType='" + contentType + "'"); resp.setContentType(contentType); } if (file != null && contentLength >= 0) { if (logger.isDebugEnabled()) logger.debug("contentLength=" + contentLength); if (contentLength < Integer.MAX_VALUE) resp.setContentLength((int) contentLength); else // Set the content-length as String to be able to use a long resp.setHeader("content-length", "" + contentLength); } InputStream renderResult = null; String relativePath = getRelativePath(req); String contextPath = req.getContextPath(); String servletPath = req.getServletPath(); String contextServletPath = contextPath + servletPath; if (folder != null && content) // Serve the directory browser for a public folder if (isContentHtml && !expectJSON) { try { folder = getService().expandFolder(folder); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } catch (RpcException e) { //We send 500 instead of 404 because this folder has been loaded before in this method and it is //impossible to not be found now resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } renderResult = renderHtml(contextServletPath, relativePath, folder, user); } // Serve the directory for an ordinary folder or for fireGSS client else try { renderResult = renderJson(user, folder); } catch (InsufficientPermissionsException e) { resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } // Copy the input stream to our output stream (if requested) if (content) { try { resp.setBufferSize(output); } catch (IllegalStateException e) { // Silent catch } try { if (file != null) if (needsContentDisposition(req)) resp.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + getDispositionFilename(file)); else resp.setHeader("Content-Disposition", "inline; filename*=UTF-8''" + getDispositionFilename(file)); if (ostream != null) copy(file, renderResult, ostream, req, oldBody); else copy(file, renderResult, writer, req, oldBody); if (file != null) updateAccounting(owner, new Date(), contentLength); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } } } else { if (ranges == null || ranges.isEmpty()) return; // Partial content response. resp.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); if (ranges.size() == 1) { Range range = (Range) ranges.get(0); resp.addHeader("Content-Range", "bytes " + range.start + "-" + range.end + "/" + range.length); long length = range.end - range.start + 1; if (length < Integer.MAX_VALUE) resp.setContentLength((int) length); else // Set the content-length as String to be able to use a long resp.setHeader("content-length", "" + length); if (contentType != null) { if (logger.isDebugEnabled()) logger.debug("contentType='" + contentType + "'"); resp.setContentType(contentType); } if (content) { try { resp.setBufferSize(output); } catch (IllegalStateException e) { // Silent catch } try { if (ostream != null) copy(file, ostream, range, req, oldBody); else copy(file, writer, range, req, oldBody); updateAccounting(owner, new Date(), contentLength); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } } } else { resp.setContentType("multipart/byteranges; boundary=" + mimeSeparation); if (content) { try { resp.setBufferSize(output); } catch (IllegalStateException e) { // Silent catch } try { if (ostream != null) copy(file, ostream, ranges.iterator(), contentType, req, oldBody); else copy(file, writer, ranges.iterator(), contentType, req, oldBody); updateAccounting(owner, new Date(), contentLength); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } } } } } /** * Handles an authentication failure. If no Authorization or Date request * parameters and no Authorization, Date or X-GSS-Date headers were present, * this is a browser request, so redirect to login and then let the user get * back to the file. Otherwise it's a bogus client request and Forbidden is * returned. */ private void handleAuthFailure(HttpServletRequest req, HttpServletResponse resp) throws IOException { if (req.getParameter(AUTHORIZATION_PARAMETER) == null && req.getParameter(DATE_PARAMETER) == null && req.getHeader(AUTHORIZATION_HEADER) == null && req.getDateHeader(DATE_HEADER) == -1 && req.getDateHeader(GSS_DATE_HEADER) == -1) resp.sendRedirect(getConfiguration().getString("loginUrl") + "?next=" + req.getRequestURL().toString()); else resp.sendError(HttpServletResponse.SC_FORBIDDEN); } /** * Return the filename of the specified file properly formatted for * including in the Content-Disposition header. */ private String getDispositionFilename(FileHeader file) throws UnsupportedEncodingException { return URLEncoder.encode(file.getName(), "UTF-8").replaceAll("\\+", "%20"); } /** * Determines whether the user agent needs the Content-Disposition * header to be set, in order to properly download a file. * * @param req the HTTP request * @return true if the Content-Disposition HTTP header must be set */ private boolean needsContentDisposition(HttpServletRequest req) { /*String agent = req.getHeader("user-agent"); if (agent != null && agent.contains("MSIE")) return true;*/ String dl = req.getParameter("dl"); if ("1".equals(dl)) return true; return false; } /** * Sends a progress update on the amount of bytes received until now for * a file that the current user is currently uploading. * * @param req the HTTP request * @param resp the HTTP response * @param parameter the value for the progress request parameter * @param user the current user * @param file the file being uploaded, or null if the request is about a new file * @throws IOException if an I/O error occurs */ private void serveProgress(HttpServletRequest req, HttpServletResponse resp, String parameter, User user, FileHeader file) throws IOException { String filename = file == null ? parameter : file.getName(); try { FileUploadStatus status = getService().getFileUploadStatus(user.getId(), filename); if (status == null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } JSONObject json = new JSONObject(); json.put("bytesUploaded", status.getBytesUploaded()).put("bytesTotal", status.getFileSize()); sendJson(req, resp, json.toString()); // Workaround for IE's broken caching behavior. resp.setHeader("Expires", "-1"); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } catch (JSONException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); return; } } /** * Server a POST request to create/modify a file or folder. * * @param req the HTTP request * @param resp the HTTP response * @exception IOException if an input/output error occurs */ void postResource(HttpServletRequest req, HttpServletResponse resp) throws IOException { boolean authDeferred = getAuthDeferred(req); if (!authDeferred && req.getParameterMap().size() > 1) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } String path = getInnerPath(req, PATH_FILES); path = path.endsWith("/") ? path : path + '/'; try { path = URLDecoder.decode(path, "UTF-8"); } catch (IllegalArgumentException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); return; } // We only defer authenticating multipart POST requests. if (authDeferred) { if (!ServletFileUpload.isMultipartContent(req)) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } handleMultipart(req, resp, path); return; } String newName = req.getParameter(NEW_FOLDER_PARAMETER); boolean hasUpdateParam = req.getParameterMap().containsKey(RESOURCE_UPDATE_PARAMETER); boolean hasTrashParam = req.getParameterMap().containsKey(RESOURCE_TRASH_PARAMETER); boolean hasRestoreParam = req.getParameterMap().containsKey(RESOURCE_RESTORE_PARAMETER); String copyTo = req.getParameter(RESOURCE_COPY_PARAMETER); String moveTo = req.getParameter(RESOURCE_MOVE_PARAMETER); String restoreVersion = req.getParameter(RESTORE_VERSION_PARAMETER); if (newName != null) { if (!isValidResourceName(newName)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } createFolder(req, resp, path, newName); } else if (hasUpdateParam) updateResource(req, resp, path); else if (hasTrashParam) trashResource(req, resp, path); else if (hasRestoreParam) restoreResource(req, resp, path); else if (copyTo != null) copyResource(req, resp, path, copyTo); else if (moveTo != null) moveResource(req, resp, path, moveTo); else if (restoreVersion != null) restoreVersion(req, resp, path, restoreVersion); else // IE with Gears uses POST for multiple uploads. putResource(req, resp); } /** * Restores a previous version for a file. * * @param req the HTTP request * @param resp the HTTP response * @param path the resource path * @param version the version number to restore * @throws IOException if an I/O error occurs */ private void restoreVersion(HttpServletRequest req, HttpServletResponse resp, String path, String version) throws IOException { final User user = getUser(req); User owner = getOwner(req); Object resource = null; try { resource = getService().getResourceAtPath(owner.getId(), path, true); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (resource instanceof Folder) { resp.sendError(HttpServletResponse.SC_CONFLICT); return; } try { final FileHeader file = (FileHeader) resource; final int oldVersion = Integer.parseInt(version); new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().restoreVersion(user.getId(), file.getId(), oldVersion); return null; } }); } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); } catch (GSSIOException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (QuotaExceededException e) { resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage()); } catch (NumberFormatException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } } /** * A method for handling multipart POST requests for uploading * files from browser-based JavaScript clients. * * @param request the HTTP request * @param response the HTTP response * @param path the resource path * @throws IOException in case an error occurs writing to the * response stream */ private void handleMultipart(HttpServletRequest request, HttpServletResponse response, String path) throws IOException { if (logger.isDebugEnabled()) logger.debug("Multipart POST for resource: " + path); User owner = getOwner(request); boolean exists = true; Object resource = null; FileHeader file = null; try { resource = getService().getResourceAtPath(owner.getId(), path, false); } catch (ObjectNotFoundException e) { exists = false; } catch (RpcException e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (exists) if (resource instanceof FileHeader) { file = (FileHeader) resource; if (file.isDeleted()) { response.sendError(HttpServletResponse.SC_CONFLICT, file.getName() + " is in the trash"); return; } } else { response.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder"); return; } Object parent; String parentPath = null; try { parentPath = getParentPath(path); parent = getService().getResourceAtPath(owner.getId(), parentPath, true); } catch (ObjectNotFoundException e) { response.sendError(HttpServletResponse.SC_NOT_FOUND, parentPath); return; } catch (RpcException e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (!(parent instanceof Folder)) { response.sendError(HttpServletResponse.SC_CONFLICT); return; } final Folder folderLocal = (Folder) parent; final String fileName = getLastElement(path); if (!isValidResourceName(fileName)) { response.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } FileItemIterator iter; File uploadedFile = null; try { // Create a new file upload handler. ServletFileUpload upload = new ServletFileUpload(); StatusProgressListener progressListener = new StatusProgressListener(getService()); upload.setProgressListener(progressListener); iter = upload.getItemIterator(request); String dateParam = null; String auth = null; while (iter.hasNext()) { FileItemStream item = iter.next(); String name = item.getFieldName(); InputStream stream = item.openStream(); if (item.isFormField()) { final String value = Streams.asString(stream); if (name.equals(DATE_PARAMETER)) dateParam = value; else if (name.equals(AUTHORIZATION_PARAMETER)) auth = value; if (logger.isDebugEnabled()) logger.debug(name + ":" + value); } else { // Fetch the timestamp used to guard against replay attacks. if (dateParam == null) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Date parameter"); return; } long timestamp; try { timestamp = DateUtil.parseDate(dateParam).getTime(); } catch (DateParseException e) { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); return; } // Fetch the Authorization parameter and find the user specified in it. if (auth == null) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "No Authorization parameter"); return; } String[] authParts = auth.split(" "); if (authParts.length != 2) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } String username = authParts[0]; String signature = authParts[1]; User user = null; try { user = getService().findUser(username); } catch (RpcException e) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (user == null) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } request.setAttribute(USER_ATTRIBUTE, user); // Remove the servlet path from the request URI. String p = request.getRequestURI(); String servletPath = request.getContextPath() + request.getServletPath(); p = p.substring(servletPath.length()); // Validate the signature in the Authorization parameter. String data = request.getMethod() + dateParam + p; if (!isSignatureValid(signature, user, data)) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return; } progressListener.setUserId(user.getId()); progressListener.setFilename(fileName); final String contentType = item.getContentType(); try { uploadedFile = getService().uploadFile(stream, user.getId()); } catch (IOException ex) { throw new GSSIOException(ex, false); } FileHeader fileLocal = null; final File upf = uploadedFile; final FileHeader f = file; final User u = user; if (file == null) fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() { @Override public FileHeader call() throws Exception { return getService().createFile(u.getId(), folderLocal.getId(), fileName, contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath()); } }); else fileLocal = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() { @Override public FileHeader call() throws Exception { return getService().updateFileContents(u.getId(), f.getId(), contentType, upf.getCanonicalFile().length(), upf.getAbsolutePath()); } }); updateAccounting(owner, new Date(), fileLocal.getCurrentBody().getFileSize()); getService().removeFileUploadProgress(user.getId(), fileName); } } // We can't return 204 here since GWT's onSubmitComplete won't fire. response.setContentType("text/html"); response.getWriter().print("<pre></pre>"); } catch (FileUploadException e) { String error = "Error while uploading file"; logger.error(error, e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); } catch (GSSIOException e) { if (uploadedFile != null && uploadedFile.exists()) uploadedFile.delete(); String error = "Error while uploading file"; if (e.logAsError()) logger.error(error, e); else logger.debug(error, e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); } catch (DuplicateNameException e) { if (uploadedFile != null && uploadedFile.exists()) uploadedFile.delete(); String error = "The specified file name already exists in this folder"; logger.error(error, e); response.sendError(HttpServletResponse.SC_CONFLICT, error); } catch (InsufficientPermissionsException e) { if (uploadedFile != null && uploadedFile.exists()) uploadedFile.delete(); String error = "You don't have the necessary permissions"; logger.error(error, e); response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, error); } catch (QuotaExceededException e) { if (uploadedFile != null && uploadedFile.exists()) uploadedFile.delete(); String error = "Not enough free space available"; if (logger.isDebugEnabled()) logger.debug(error, e); response.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, error); } catch (ObjectNotFoundException e) { if (uploadedFile != null && uploadedFile.exists()) uploadedFile.delete(); String error = "A specified object was not found"; logger.error(error, e); response.sendError(HttpServletResponse.SC_NOT_FOUND, error); } catch (RpcException e) { if (uploadedFile != null && uploadedFile.exists()) uploadedFile.delete(); String error = "An error occurred while communicating with the service"; logger.error(error, e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); } catch (Exception e) { if (uploadedFile != null && uploadedFile.exists()) uploadedFile.delete(); String error = "An internal server error occurred"; logger.error(error, e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); } } /** * Move the resource in the specified path to the specified destination. * * @param req the HTTP request * @param resp the HTTP response * @param path the path of the resource * @param moveTo the destination of the move procedure * @throws IOException if an input/output error occurs */ private void moveResource(HttpServletRequest req, HttpServletResponse resp, String path, String moveTo) throws IOException { final User user = getUser(req); User owner = getOwner(req); Object resource = null; try { resource = getService().getResourceAtPath(owner.getId(), path, true); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } String destination = null; User destOwner = null; boolean exists = true; try { destination = getDestinationPath(req, encodePath(moveTo)); destination = URLDecoder.decode(destination, "UTF-8"); destOwner = getDestinationOwner(req); getService().getResourceAtPath(destOwner.getId(), destination, true); } catch (ObjectNotFoundException e) { exists = false; } catch (URISyntaxException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination); return; } if (exists) { resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists"); return; } try { final User dOwner = destOwner; final String dest = destination; if (resource instanceof Folder) { final Folder folderLocal = (Folder) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().moveFolderToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest); return null; } }); } else { final FileHeader fileLocal = (FileHeader) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().moveFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest); return null; } }); } } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination); } catch (DuplicateNameException e) { resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage()); } catch (GSSIOException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (QuotaExceededException e) { resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage()); } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination); } } /** * Copy the resource in the specified path to the specified destination. * * @param req the HTTP request * @param resp the HTTP response * @param path the path of the resource * @param copyTo the destination of the copy procedure * @throws IOException if an input/output error occurs */ private void copyResource(HttpServletRequest req, HttpServletResponse resp, String path, String copyTo) throws IOException { final User user = getUser(req); User owner = getOwner(req); Object resource = null; try { resource = getService().getResourceAtPath(owner.getId(), path, true); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } String destination = null; User destOwner = null; boolean exists = true; try { String destinationEncoded = getDestinationPath(req, encodePath(copyTo)); destination = URLDecoder.decode(destinationEncoded, "UTF-8"); destOwner = getDestinationOwner(req); getService().getResourceAtPath(destOwner.getId(), destinationEncoded, true); } catch (ObjectNotFoundException e) { exists = false; } catch (URISyntaxException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination); return; } if (exists) { resp.sendError(HttpServletResponse.SC_CONFLICT, destination + " already exists"); return; } try { final User dOwner = destOwner; final String dest = destination; if (resource instanceof Folder) { final Folder folderLocal = (Folder) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().copyFolderStructureToPath(user.getId(), dOwner.getId(), folderLocal.getId(), dest); return null; } }); } else { final FileHeader fileLocal = (FileHeader) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().copyFileToPath(user.getId(), dOwner.getId(), fileLocal.getId(), dest); return null; } }); } } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination); } catch (DuplicateNameException e) { resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage()); } catch (GSSIOException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (QuotaExceededException e) { resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage()); } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, destination); } } private String encodePath(String path) throws UnsupportedEncodingException { StringTokenizer str = new StringTokenizer(path, "/:", true); String result = new String(); while (str.hasMoreTokens()) { String token = str.nextToken(); if (!token.equals("/") && !token.equals(":")) token = URLEncoder.encode(token, "UTF-8"); result = result + token; } return result; } /** * A helper method that extracts the relative resource path, * after removing the 'files' namespace. * The path returned is <i>not</i> URL-decoded. * * @param req the HTTP request * @param path the specified path * @return the path relative to the root folder * @throws URISyntaxException * @throws RpcException in case an error occurs while communicating * with the backend * @throws UnsupportedEncodingException */ private String getDestinationPath(HttpServletRequest req, String path) throws URISyntaxException, RpcException, UnsupportedEncodingException { URI uri = new URI(path); String dest = uri.getRawPath(); // Remove the context path from the destination URI. String contextPath = req.getContextPath(); if (!dest.startsWith(contextPath)) throw new URISyntaxException(dest, "Destination path does not start with " + contextPath); dest = dest.substring(contextPath.length()); // Remove the servlet path from the destination URI. String servletPath = req.getServletPath(); if (!dest.startsWith(servletPath)) throw new URISyntaxException(dest, "Destination path does not start with " + servletPath); dest = dest.substring(servletPath.length()); // Strip the username part if (dest.length() < 2) throw new URISyntaxException(dest, "No username in the destination URI"); int slash = dest.substring(1).indexOf('/'); if (slash == -1) throw new URISyntaxException(dest, "No username in the destination URI"); // Decode the user to get the proper characters (mainly the @) String owner = URLDecoder.decode(dest.substring(1, slash + 1), "UTF-8"); User o; o = getService().findUser(owner); if (o == null) throw new URISyntaxException(dest, "User " + owner + " not found"); req.setAttribute(DESTINATION_OWNER_ATTRIBUTE, o); dest = dest.substring(slash + 1); // Chop the resource namespace part dest = dest.substring(RequestHandler.PATH_FILES.length()); dest = dest.endsWith("/") ? dest : dest + '/'; return dest; } /** * Move the resource in the specified path to the trash bin. * * @param req the HTTP request * @param resp the HTTP response * @param path the path of the resource * @throws IOException if an input/output error occurs */ private void trashResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException { final User user = getUser(req); User owner = getOwner(req); Object resource = null; try { resource = getService().getResourceAtPath(owner.getId(), path, true); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } try { if (resource instanceof Folder) { final Folder folderLocal = (Folder) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().moveFolderToTrash(user.getId(), folderLocal.getId()); return null; } }); } else { final FileHeader fileLocal = (FileHeader) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().moveFileToTrash(user.getId(), fileLocal.getId()); return null; } }); } } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); } } /** * Restore the resource in the specified path from the trash bin. * * @param req the HTTP request * @param resp the HTTP response * @param path the path of the resource * @throws IOException if an input/output error occurs */ private void restoreResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException { final User user = getUser(req); User owner = getOwner(req); Object resource = null; try { resource = getService().getResourceAtPath(owner.getId(), path, false); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } try { if (resource instanceof Folder) { final Folder folderLocal = (Folder) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().removeFolderFromTrash(user.getId(), folderLocal.getId()); return null; } }); } else { final FileHeader fileLocal = (FileHeader) resource; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().removeFileFromTrash(user.getId(), fileLocal.getId()); return null; } }); } } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); } } /** * Update the resource in the specified path. * * @param req the HTTP request * @param resp the HTTP response * @param path the path of the resource * @throws IOException if an input/output error occurs */ private void updateResource(HttpServletRequest req, HttpServletResponse resp, String path) throws IOException { final User user = getUser(req); User owner = getOwner(req); Object resource = null; try { resource = getService().getResourceAtPath(owner.getId(), path, false); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, path); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } StringBuffer input = new StringBuffer(); JSONObject json = null; if (req.getContentType() != null && req.getContentType().startsWith("application/x-www-form-urlencoded")) input.append(req.getParameter(RESOURCE_UPDATE_PARAMETER)); else { // Assume application/json BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream(), "UTF-8")); String line = null; while ((line = reader.readLine()) != null) input.append(line); reader.close(); } try { json = new JSONObject(input.toString()); if (logger.isDebugEnabled()) logger.debug("JSON update: " + json); if (resource instanceof Folder) { final Folder folderLocal = (Folder) resource; String name = json.optString("name"); if (!isValidResourceName(name)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } JSONArray permissions = json.optJSONArray("permissions"); Set<Permission> perms = null; if (permissions != null) perms = parsePermissions(user, permissions); Boolean readForAll = null; if (json.opt("readForAll") != null) readForAll = json.optBoolean("readForAll"); if (!name.isEmpty() || permissions != null || readForAll != null) { final String fName = name.isEmpty() ? null : name; final Boolean freadForAll = readForAll; final Set<Permission> fPerms = perms; Folder folderUpdated = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() { @Override public Folder call() throws Exception { return getService().updateFolder(user.getId(), folderLocal.getId(), fName, freadForAll, fPerms); } }); resp.getWriter().println(getNewUrl(req, folderUpdated)); } } else { final FileHeader fileLocal = (FileHeader) resource; String name = null; if (json.opt("name") != null) name = json.optString("name"); if (name != null) if (!isValidResourceName(name)) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } Long modificationDate = null; if (json.optLong("modificationDate") != 0) modificationDate = json.optLong("modificationDate"); Boolean versioned = null; if (json.opt("versioned") != null) versioned = json.getBoolean("versioned"); JSONArray tagset = json.optJSONArray("tags"); String tags = null; StringBuffer t = new StringBuffer(); if (tagset != null) { for (int i = 0; i < tagset.length(); i++) t.append(tagset.getString(i) + ','); tags = t.toString(); } JSONArray permissions = json.optJSONArray("permissions"); Set<Permission> perms = null; if (permissions != null) perms = parsePermissions(user, permissions); Boolean readForAll = null; if (json.opt("readForAll") != null) readForAll = json.optBoolean("readForAll"); if (name != null || tags != null || modificationDate != null || versioned != null || perms != null || readForAll != null) { final String fName = name; final String fTags = tags; final Date mDate = modificationDate != null ? new Date(modificationDate) : null; final Boolean fVersioned = versioned; final Boolean fReadForAll = readForAll; final Set<Permission> fPerms = perms; new TransactionHelper<Object>().tryExecute(new Callable<Object>() { @Override public Object call() throws Exception { getService().updateFile(user.getId(), fileLocal.getId(), fName, fTags, mDate, fVersioned, fReadForAll, fPerms); return null; } }); } } } catch (JSONException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (DuplicateNameException e) { resp.sendError(HttpServletResponse.SC_CONFLICT, e.getMessage()); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } } /** * Returns the new URL of an updated folder. */ private String getNewUrl(HttpServletRequest req, Folder folder) throws UnsupportedEncodingException { String parentUrl = URLDecoder.decode(getContextPath(req, true), "UTF-8"); String fpath = URLDecoder.decode(getRelativePath(req), "UTF-8"); if (parentUrl.indexOf(fpath) != -1) parentUrl = parentUrl.substring(0, parentUrl.indexOf(fpath)); if (!parentUrl.endsWith("/")) parentUrl = parentUrl + "/"; parentUrl = parentUrl + folder.getOwner().getUsername() + PATH_FILES + folder.getPath(); return parentUrl; } /** * Helper method to convert a JSON array of permissions into a set of * Permission objects. * * @param user the current user * @param permissions the JSON array to parse * @return the parsed set of permissions * @throws JSONException if there was an error parsing the JSON object * @throws RpcException if there was an error communicating with the EJB * @throws ObjectNotFoundException if the user could not be found * @throws UnsupportedEncodingException */ private Set<Permission> parsePermissions(User user, JSONArray permissions) throws JSONException, RpcException, ObjectNotFoundException, UnsupportedEncodingException { if (permissions == null) return null; Set<Permission> perms = new HashSet<Permission>(); for (int i = 0; i < permissions.length(); i++) { JSONObject j = permissions.getJSONObject(i); Permission perm = new Permission(); perm.setModifyACL(j.optBoolean("modifyACL")); perm.setRead(j.optBoolean("read")); perm.setWrite(j.optBoolean("write")); String permUser = j.optString("user"); if (!permUser.isEmpty()) { User u = getService().findUser(permUser); if (u == null) throw new ObjectNotFoundException("User " + permUser + " not found"); perm.setUser(u); } // 31/8/2009: Add optional groupUri which takes priority if it exists String permGroupUri = j.optString("groupUri"); String permGroup = j.optString("group"); if (!permGroupUri.isEmpty()) { String[] names = permGroupUri.split("/"); String grp = URLDecoder.decode(names[names.length - 1], "UTF-8"); String usr = URLDecoder.decode(names[names.length - 3], "UTF-8"); User u = getService().findUser(usr); if (u == null) throw new ObjectNotFoundException("User " + permUser + " not found"); Group g = getService().getGroup(u.getId(), grp); perm.setGroup(g); } else if (!permGroup.isEmpty()) { Group g = getService().getGroup(user.getId(), permGroup); perm.setGroup(g); } if (permUser.isEmpty() && permGroupUri.isEmpty() && permGroup.isEmpty()) throw new JSONException("A permission must correspond to either a user or a group"); perms.add(perm); } return perms; } /** * Creates a new folder with the specified name under the folder in the provided path. * * @param req the HTTP request * @param resp the HTTP response * @param path the parent folder path * @param folderName the name of the new folder * @throws IOException if an input/output error occurs */ private void createFolder(HttpServletRequest req, HttpServletResponse resp, String path, final String folderName) throws IOException { if (logger.isDebugEnabled()) logger.debug("Creating folder " + folderName + " in '" + path); final User user = getUser(req); User owner = getOwner(req); boolean exists = true; try { getService().getResourceAtPath(owner.getId(), path + folderName, false); } catch (ObjectNotFoundException e) { exists = false; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName); return; } if (exists) { resp.addHeader("Allow", METHOD_GET + ", " + METHOD_DELETE + ", " + METHOD_HEAD); resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } Object parent; try { parent = getService().getResourceAtPath(owner.getId(), path, true); } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_CONFLICT); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName); return; } try { if (parent instanceof Folder) { final Folder folderLocal = (Folder) parent; Folder newFolder = new TransactionHelper<Folder>().tryExecute(new Callable<Folder>() { @Override public Folder call() throws Exception { return getService().createFolder(user.getId(), folderLocal.getId(), folderName); } }); String newResource = getApiRoot() + newFolder.getURI(); resp.setHeader("Location", newResource); resp.setContentType("text/plain"); PrintWriter out = resp.getWriter(); out.println(newResource); } else { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } } catch (DuplicateNameException e) { resp.sendError(HttpServletResponse.SC_CONFLICT); return; } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_CONFLICT); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path + folderName); return; } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } resp.setStatus(HttpServletResponse.SC_CREATED); } /** * @param req * @param resp * @throws IOException * @throws FileNotFoundException */ void putResource(HttpServletRequest req, HttpServletResponse resp) throws IOException, FileNotFoundException { String path = getInnerPath(req, PATH_FILES); try { path = URLDecoder.decode(path, "UTF-8"); } catch (IllegalArgumentException e) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); return; } if (logger.isDebugEnabled()) logger.debug("Updating resource: " + path); final User user = getUser(req); User owner = getOwner(req); boolean exists = true; Object resource = null; FileHeader fileLocal = null; try { resource = getService().getResourceAtPath(owner.getId(), path, false); } catch (ObjectNotFoundException e) { exists = false; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (exists) if (resource instanceof FileHeader) fileLocal = (FileHeader) resource; else { resp.sendError(HttpServletResponse.SC_CONFLICT, path + " is a folder"); return; } boolean result = true; // Temporary content file used to support partial PUT. File contentFile = null; Range range = parseContentRange(req, resp); InputStream resourceInputStream = null; // Append data specified in ranges to existing content for this // resource - create a temporary file on the local filesystem to // perform this operation. // Assume just one range is specified for now if (range != null) { try { contentFile = executePartialPut(req, range, path); } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } catch (ObjectNotFoundException e) { resp.sendError(HttpServletResponse.SC_CONFLICT); return; } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } resourceInputStream = new FileInputStream(contentFile); } else resourceInputStream = req.getInputStream(); try { Folder folderLocal = null; Object parent = getService().getResourceAtPath(owner.getId(), getParentPath(path), true); if (!(parent instanceof Folder)) { resp.sendError(HttpServletResponse.SC_CONFLICT); return; } folderLocal = (Folder) parent; final String name = getLastElement(path); final String mimeType = context.getMimeType(name); File uploadedFile = null; try { uploadedFile = getService().uploadFile(resourceInputStream, user.getId()); } catch (IOException ex) { throw new GSSIOException(ex, false); } FileHeader fileTemp = null; final File uploadedf = uploadedFile; final Folder parentf = folderLocal; final FileHeader f = fileLocal; if (exists) fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() { @Override public FileHeader call() throws Exception { return getService().updateFileContents(user.getId(), f.getId(), mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath()); } }); else fileTemp = new TransactionHelper<FileHeader>().tryExecute(new Callable<FileHeader>() { @Override public FileHeader call() throws Exception { return getService().createFile(user.getId(), parentf.getId(), name, mimeType, uploadedf.getCanonicalFile().length(), uploadedf.getAbsolutePath()); } }); updateAccounting(owner, new Date(), fileTemp.getCurrentBody().getFileSize()); getService().removeFileUploadProgress(user.getId(), fileTemp.getName()); } catch (ObjectNotFoundException e) { result = false; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } catch (IOException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } catch (GSSIOException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } catch (DuplicateNameException e) { resp.sendError(HttpServletResponse.SC_CONFLICT); return; } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } catch (QuotaExceededException e) { resp.sendError(HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, e.getMessage()); return; } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, path); return; } if (result) { if (exists) resp.setStatus(HttpServletResponse.SC_NO_CONTENT); else resp.setStatus(HttpServletResponse.SC_CREATED); } else resp.sendError(HttpServletResponse.SC_CONFLICT); } /** * Delete a resource. * * @param req The servlet request we are processing * @param resp The servlet response we are processing * @throws IOException if the response cannot be sent */ void deleteResource(HttpServletRequest req, HttpServletResponse resp) throws IOException { String path = getInnerPath(req, PATH_FILES); if (logger.isDebugEnabled()) logger.debug("Deleting resource '" + path); path = URLDecoder.decode(path, "UTF-8"); final User user = getUser(req); User owner = getOwner(req); boolean exists = true; Object object = null; try { object = getService().getResourceAtPath(owner.getId(), path, false); } catch (ObjectNotFoundException e) { exists = false; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } if (!exists) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } Folder folderLocal = null; FileHeader fileLocal = null; if (object instanceof Folder) folderLocal = (Folder) object; else fileLocal = (FileHeader) object; if (fileLocal != null) try { final FileHeader f = fileLocal; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().deleteFile(user.getId(), f.getId()); return null; } }); } catch (InsufficientPermissionsException e) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } catch (ObjectNotFoundException e) { // Although we had already found the object, it was // probably deleted from another thread. resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } catch (RpcException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } catch (Exception e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } else if (folderLocal != null) try { final Folder fo = folderLocal; new TransactionHelper<Void>().tryExecute(new Callable<Void>() { @Override public Void call() throws Exception { getService().deleteFolder(user.getId(), fo.getId()); return null; } }); } catch (InsufficientPermissionsException e) { resp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED); return; } catch (ObjectNotFoundException e) { resp.setStatus(HttpServletResponse.SC_NOT_FOUND); return; } catch (RpcException e) { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } catch (Exception e) { resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } resp.setStatus(HttpServletResponse.SC_NO_CONTENT); return; } /** * Return an InputStream to a JSON representation of the contents * of this directory. * * @param user the user that made the request * @param folder the specified directory * @return an input stream with the rendered contents * @throws IOException if the response cannot be sent * @throws ServletException * @throws InsufficientPermissionsException if the user does not have * the necessary privileges to read the directory */ private InputStream renderJson(User user, Folder folder) throws IOException, ServletException, InsufficientPermissionsException { try { folder = getService().expandFolder(folder); } catch (ObjectNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (RpcException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } JSONObject json = new JSONObject(); try { json.put("name", folder.getName()).put("owner", folder.getOwner().getUsername()) .put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()) .put("creationDate", folder.getAuditInfo().getCreationDate().getTime()) .put("deleted", folder.isDeleted()).put("shared", folder.getShared()) .put("readForAll", folder.isReadForAll()); if (folder.getAuditInfo().getModifiedBy() != null) json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).put("modificationDate", folder.getAuditInfo().getModificationDate().getTime()); if (folder.getParent() != null) { JSONObject j = new JSONObject(); j.put("uri", getApiRoot() + folder.getParent().getURI()); j.put("name", folder.getParent().getName()); json.put("parent", j); } List<JSONObject> subfolders = new ArrayList<JSONObject>(); for (Folder f : folder.getSubfolders()) if (!f.isDeleted()) { JSONObject j = new JSONObject(); j.put("name", f.getName()).put("uri", getApiRoot() + f.getURI()).put("shared", f.getShared()); subfolders.add(j); } json.put("folders", subfolders); List<JSONObject> files = new ArrayList<JSONObject>(); List<FileHeader> fileHeaders = getService().getFiles(user.getId(), folder.getId(), false); for (FileHeader f : fileHeaders) { JSONObject j = new JSONObject(); j.put("name", f.getName()).put("owner", f.getOwner().getUsername()).put("deleted", f.isDeleted()) .put("version", f.getCurrentBody().getVersion()) .put("content", f.getCurrentBody().getMimeType()) .put("size", f.getCurrentBody().getFileSize()).put("shared", f.getShared()) .put("versioned", f.isVersioned()) .put("creationDate", f.getAuditInfo().getCreationDate().getTime()) .put("path", f.getFolder().getPath()).put("uri", getApiRoot() + f.getURI()); if (f.getAuditInfo().getModificationDate() != null) j.put("modificationDate", f.getAuditInfo().getModificationDate().getTime()); files.add(j); } json.put("files", files); Set<Permission> perms = getService().getFolderPermissions(user.getId(), folder.getId()); json.put("permissions", renderJson(perms)); } catch (JSONException e) { throw new ServletException(e); } catch (ObjectNotFoundException e) { throw new ServletException(e); } catch (RpcException e) { throw new ServletException(e); } // Prepare a writer to a buffered area ByteArrayOutputStream stream = new ByteArrayOutputStream(); OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8"); PrintWriter writer = new PrintWriter(osWriter); // Return an input stream to the underlying bytes writer.write(json.toString()); writer.flush(); return new ByteArrayInputStream(stream.toByteArray()); } /** * Return a String with a JSON representation of the metadata * of the specified folder. * @throws RpcException * @throws InsufficientPermissionsException * @throws ObjectNotFoundException */ private String renderJsonMetadata(User user, Folder folder) throws ServletException, InsufficientPermissionsException { // Check if the user has read permission. try { if (!getService().canReadFolder(user.getId(), folder.getId())) throw new InsufficientPermissionsException(); } catch (ObjectNotFoundException e) { throw new ServletException(e); } catch (RpcException e) { throw new ServletException(e); } JSONObject json = new JSONObject(); try { json.put("name", URLEncoder.encode(folder.getName(), "UTF-8")) .put("owner", folder.getOwner().getUsername()) .put("createdBy", folder.getAuditInfo().getCreatedBy().getUsername()) .put("creationDate", folder.getAuditInfo().getCreationDate().getTime()) .put("deleted", folder.isDeleted()); if (folder.getAuditInfo().getModifiedBy() != null) json.put("modifiedBy", folder.getAuditInfo().getModifiedBy().getUsername()).put("modificationDate", folder.getAuditInfo().getModificationDate().getTime()); } catch (JSONException e) { throw new ServletException(e); } catch (UnsupportedEncodingException e) { throw new ServletException(e); } return json.toString(); } /** * Return a String with a JSON representation of the metadata * of the specified file. If an old file body is provided, then * the metadata of that particular version will be returned. * * @param user the user that made the request * @param file the specified file header * @param oldBody the version number * @return the JSON-encoded file * @throws ServletException * @throws InsufficientPermissionsException if the user does not have * the necessary privileges to read the directory */ private String renderJson(User user, FileHeader file, FileBody oldBody) throws ServletException, InsufficientPermissionsException { JSONObject json = new JSONObject(); try { file = getService().expandFile(file); // Need to encode file name in order to properly display it in the web client. json.put("name", URLEncoder.encode(file.getName(), "UTF-8")).put("owner", file.getOwner().getUsername()) .put("versioned", file.isVersioned()) .put("version", oldBody != null ? oldBody.getVersion() : file.getCurrentBody().getVersion()) .put("readForAll", file.isReadForAll()).put("shared", file.getShared()) .put("tags", renderJson(file.getFileTagsAsStrings())).put("path", file.getFolder().getPath()) .put("uri", getApiRoot() + file.getURI()).put("deleted", file.isDeleted()); JSONObject j = new JSONObject(); j.put("uri", getApiRoot() + file.getFolder().getURI()).put("name", URLEncoder.encode(file.getFolder().getName(), "UTF-8")); json.put("folder", j); if (oldBody != null) json.put("createdBy", oldBody.getAuditInfo().getCreatedBy().getUsername()) .put("creationDate", oldBody.getAuditInfo().getCreationDate().getTime()) .put("modifiedBy", oldBody.getAuditInfo().getModifiedBy().getUsername()) .put("modificationDate", oldBody.getAuditInfo().getModificationDate().getTime()) .put("content", oldBody.getMimeType()).put("size", oldBody.getFileSize()); else json.put("createdBy", file.getAuditInfo().getCreatedBy().getUsername()) .put("creationDate", file.getAuditInfo().getCreationDate().getTime()) .put("modifiedBy", file.getAuditInfo().getModifiedBy().getUsername()) .put("modificationDate", file.getAuditInfo().getModificationDate().getTime()) .put("content", file.getCurrentBody().getMimeType()) .put("size", file.getCurrentBody().getFileSize()); Set<Permission> perms = getService().getFilePermissions(user.getId(), file.getId()); json.put("permissions", renderJson(perms)); } catch (JSONException e) { throw new ServletException(e); } catch (ObjectNotFoundException e) { throw new ServletException(e); } catch (RpcException e) { throw new ServletException(e); } catch (UnsupportedEncodingException e) { throw new ServletException(e); } return json.toString(); } /** * Return a String with a JSON representation of the * specified set of permissions. * * @param permissions the set of permissions * @return the JSON-encoded object * @throws JSONException * @throws UnsupportedEncodingException */ private JSONArray renderJson(Set<Permission> permissions) throws JSONException, UnsupportedEncodingException { JSONArray perms = new JSONArray(); for (Permission p : permissions) { JSONObject permission = new JSONObject(); permission.put("read", p.hasRead()).put("write", p.hasWrite()).put("modifyACL", p.hasModifyACL()); if (p.getUser() != null) permission.put("user", p.getUser().getUsername()); if (p.getGroup() != null) { Group group = p.getGroup(); permission.put("groupUri", getApiRoot() + group.getOwner().getUsername() + PATH_GROUPS + "/" + URLEncoder.encode(group.getName(), "UTF-8")); permission.put("group", URLEncoder.encode(p.getGroup().getName(), "UTF-8")); } perms.put(permission); } return perms; } /** * Return a String with a JSON representation of the * specified collection of tags. * * @param tags the collection of tags * @return the JSON-encoded object * @throws JSONException * @throws UnsupportedEncodingException */ private JSONArray renderJson(Collection<String> tags) throws JSONException, UnsupportedEncodingException { JSONArray tagArray = new JSONArray(); for (String t : tags) tagArray.put(URLEncoder.encode(t, "UTF-8")); return tagArray; } /** * Retrieves the user who owns the destination namespace, for a * copy or move request. * * @param req the HTTP request * @return the owner of the namespace */ protected User getDestinationOwner(HttpServletRequest req) { return (User) req.getAttribute(DESTINATION_OWNER_ATTRIBUTE); } /** * A helper inner class for updating the progress status of a file upload. * * @author kman */ public static class StatusProgressListener implements ProgressListener { private int percentLogged = 0; private long bytesTransferred = 0; private long fileSize = -100; private Long userId; private String filename; private ExternalAPI service; public StatusProgressListener(ExternalAPI aService) { service = aService; } /** * Modify the userId. * * @param aUserId the userId to set */ public void setUserId(Long aUserId) { userId = aUserId; } /** * Modify the filename. * * @param aFilename the filename to set */ public void setFilename(String aFilename) { filename = aFilename; } @Override public void update(long bytesRead, long contentLength, int items) { //monitoring per percent of bytes uploaded bytesTransferred = bytesRead; if (fileSize != contentLength) fileSize = contentLength; int percent = new Long(bytesTransferred * 100 / fileSize).intValue(); if (percent < 5 || percent % TRACK_PROGRESS_PERCENT == 0) if (percent != percentLogged) { percentLogged = percent; try { if (userId != null && filename != null) service.createFileUploadProgress(userId, filename, bytesTransferred, fileSize); } catch (ObjectNotFoundException e) { // Swallow the exception since it is going to be caught // by previously called methods } } } } /** * Return an InputStream to an HTML representation of the contents of this * directory. * * @param contextPath Context path to which our internal paths are relative * @param path the requested path to the resource * @param folder the specified directory * @param user the specified user * @return an input stream with the rendered contents * @throws IOException * @throws ServletException */ private InputStream renderHtml(String contextPath, String path, Folder folder, User user) throws IOException, ServletException { String name = folder.getName(); // Prepare a writer to a buffered area ByteArrayOutputStream stream = new ByteArrayOutputStream(); OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8"); PrintWriter writer = new PrintWriter(osWriter); StringBuffer sb = new StringBuffer(); // rewriteUrl(contextPath) is expensive. cache result for later reuse String rewrittenContextPath = rewriteUrl(contextPath); // Render the page header sb.append("<html>\r\n"); sb.append("<head>\r\n"); sb.append("<title>"); sb.append("Index of " + name); sb.append("</title>\r\n"); sb.append("<STYLE><!--"); sb.append(GSS_CSS); sb.append("--></STYLE> "); sb.append("</head>\r\n"); sb.append("<body>"); sb.append("<h1>"); sb.append("Index of " + name); // Render the link to our parent (if required) String parentDirectory = path; if (parentDirectory.endsWith("/")) parentDirectory = parentDirectory.substring(0, parentDirectory.length() - 1); int slash = parentDirectory.lastIndexOf('/'); if (slash >= 0) { String parent = path.substring(0, slash); sb.append(" - <a href=\""); sb.append(rewrittenContextPath); if (parent.equals("")) parent = "/"; sb.append(parent); if (!parent.endsWith("/")) sb.append("/"); sb.append("\">"); sb.append("<b>"); sb.append("Up To " + parent); sb.append("</b>"); sb.append("</a>"); } sb.append("</h1>"); sb.append("<HR size=\"1\" noshade=\"noshade\">"); sb.append("<table width=\"100%\" cellspacing=\"0\"" + " cellpadding=\"5\" align=\"center\">\r\n"); // Render the column headings sb.append("<tr>\r\n"); sb.append("<td align=\"left\"><font size=\"+1\"><strong>"); sb.append("Name"); sb.append("</strong></font></td>\r\n"); sb.append("<td align=\"center\"><font size=\"+1\"><strong>"); sb.append("Size"); sb.append("</strong></font></td>\r\n"); sb.append("<td align=\"right\"><font size=\"+1\"><strong>"); sb.append("Last modified"); sb.append("</strong></font></td>\r\n"); sb.append("</tr>"); // Render the directory entries within this directory boolean shade = false; Iterator iter = folder.getSubfolders().iterator(); while (iter.hasNext()) { Folder subf = (Folder) iter.next(); if (subf.isReadForAll() && !subf.isDeleted()) { String resourceName = subf.getName(); if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF")) continue; sb.append("<tr"); if (shade) sb.append(" bgcolor=\"#eeeeee\""); sb.append(">\r\n"); shade = !shade; sb.append("<td align=\"left\"> \r\n"); sb.append("<a href=\""); sb.append(rewrittenContextPath + path); sb.append(rewriteUrl(resourceName)); sb.append("/"); sb.append("\"><tt>"); sb.append(RequestUtil.filter(resourceName)); sb.append("/"); sb.append("</tt></a></td>\r\n"); sb.append("<td align=\"right\"><tt>"); sb.append(" "); sb.append("</tt></td>\r\n"); sb.append("<td align=\"right\"><tt>"); sb.append(getLastModifiedHttp(folder.getAuditInfo())); sb.append("</tt></td>\r\n"); sb.append("</tr>\r\n"); } } List<FileHeader> files; try { files = getService().getFiles(user.getId(), folder.getId(), true); } catch (ObjectNotFoundException e) { throw new ServletException(e.getMessage()); } catch (InsufficientPermissionsException e) { throw new ServletException(e.getMessage()); } catch (RpcException e) { throw new ServletException(e.getMessage()); } for (FileHeader file : files) //Display only file resources that are marked as public and are not deleted if (file.isReadForAll() && !file.isDeleted()) { String resourceName = file.getName(); if (resourceName.equalsIgnoreCase("WEB-INF") || resourceName.equalsIgnoreCase("META-INF")) continue; sb.append("<tr"); if (shade) sb.append(" bgcolor=\"#eeeeee\""); sb.append(">\r\n"); shade = !shade; sb.append("<td align=\"left\"> \r\n"); sb.append("<a href=\""); sb.append(rewrittenContextPath + path); sb.append(rewriteUrl(resourceName)); sb.append("\"><tt>"); sb.append(RequestUtil.filter(resourceName)); sb.append("</tt></a></td>\r\n"); sb.append("<td align=\"right\"><tt>"); sb.append(renderSize(file.getCurrentBody().getFileSize())); sb.append("</tt></td>\r\n"); sb.append("<td align=\"right\"><tt>"); sb.append(getLastModifiedHttp(file.getAuditInfo())); sb.append("</tt></td>\r\n"); sb.append("</tr>\r\n"); } // Render the page footer sb.append("</table>\r\n"); sb.append("<HR size=\"1\" noshade=\"noshade\">"); sb.append("</body>\r\n"); sb.append("</html>\r\n"); // Return an input stream to the underlying bytes writer.write(sb.toString()); writer.flush(); return new ByteArrayInputStream(stream.toByteArray()); } }