/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program 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 version 2 of the License. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.gui.download; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rhq.core.domain.cloud.Server.OperationMode; import org.rhq.core.util.stream.StreamUtil; import org.rhq.enterprise.server.util.LookupUtil; /** * Serves the static content found in rhq-downloads. */ @WebServlet(urlPatterns = {"/*"}, loadOnStartup = 1) public class DownloadServlet extends HttpServlet { private static final long serialVersionUID = 1L; // the system property that disables/enables file listing - default is to show the listing private static String SYSPROP_SHOW_DOWNLOADS_LISTING = "rhq.server.show-downloads-listing"; private static int numActiveDownloads = 0; private static boolean showDownloadsListing; private Log log = LogFactory.getLog(this.getClass()); @Override public void init() throws ServletException { log.info("Starting the RHQ download servlet..."); String propValue = System.getProperty(SYSPROP_SHOW_DOWNLOADS_LISTING, "true"); showDownloadsListing = Boolean.parseBoolean(propValue); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // seeing odd browser caching issues, even though we set Last-Modified. so force no caching for now disableBrowserCache(resp); if (isServerAcceptingRequests()) { String requestedDirectory = getRequestedDirectory(req); if (requestedDirectory == null) { numActiveDownloads++; download(req, resp); numActiveDownloads--; } else { if (showDownloadsListing) { outputFileList(requestedDirectory, req, resp); } else { disableBrowserCache(resp); resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Listing disabled"); } } } else { sendErrorServerNotAcceptingRequests(resp); } return; } /** * Returns the relative path of the requested directory but only if it exists in * one of the root directories. If the requested path is a file or the directory * doesn't exist under a root directory, null is returned. * * @param req * @return relative path of the directory being requested * @throws ServletException */ private String getRequestedDirectory(HttpServletRequest req) throws ServletException { String pathInfo = req.getPathInfo(); File[] rootDownloadsDirs; try { rootDownloadsDirs = getRootDownloadsDirs(); } catch (Throwable t) { throw new ServletException(t); } if (pathInfo == null || pathInfo.equals("") || pathInfo.equals("/")) { return ""; } for (File rootDownloadsDir : rootDownloadsDirs) { File downloadDir = new File(rootDownloadsDir, pathInfo); if (downloadDir.isDirectory()) { return pathInfo; } } // either the path does not exist or its a file, not a directory return null; } private void download(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { if (isForbiddenPath(req.getPathInfo(), resp)) { return; } // look for the file in one of the root download directories File fileToDownload = null; File[] downloadDirs = getRootDownloadsDirs(); for (File downloadDir : downloadDirs) { File file = new File(downloadDir, req.getPathInfo()); if (file.exists()) { fileToDownload = file; break; } } if (fileToDownload == null) { disableBrowserCache(resp); resp.sendError(HttpServletResponse.SC_NOT_FOUND, "File does not exist: " + req.getPathInfo()); return; } resp.setContentType(getMimeType(fileToDownload)); resp.setHeader("Content-Disposition", "attachment; filename=" + fileToDownload.getName()); resp.setContentLength((int) fileToDownload.length()); resp.setDateHeader("Last-Modified", fileToDownload.lastModified()); FileInputStream stream = new FileInputStream(fileToDownload); try { StreamUtil.copy(stream, resp.getOutputStream(), false); } finally { stream.close(); } } catch (Throwable t) { log.error("Failed to stream download content.", t); disableBrowserCache(resp); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to download content"); } return; } private String getMimeType(File file) { String mimeType = null; try { mimeType = getServletContext().getMimeType(file.getName()); } catch (Throwable t) { // i'm paranoid, no idea if this will ever happen, but its not fatal so keep going log.warn("Failed to get mime type for [" + file + "].", t); } if (mimeType == null) { mimeType = "application/octet-stream"; } return mimeType; } private void outputFileList(String requestedDirectory, HttpServletRequest req, HttpServletResponse resp) throws ServletException { try { if (!isForbiddenPath(requestedDirectory, resp)) { File requestedDirectoryRelativePath = new File(requestedDirectory); String dirName = requestedDirectoryRelativePath.getName(); disableBrowserCache(resp); resp.setContentType("text/html"); PrintWriter writer = resp.getWriter(); writer.println(String.format("<html><head><title>Available Downloads: %s</title></head>", dirName)); writer.println("<body><h1>Available Downloads</h1>"); writer.println(String.format("<font size=\"+2\"><b><pre>%s</pre></b></font>", dirName)); List<File> files = getDownloadFiles(requestedDirectory); if (files.size() > 0) { String pathInfo = req.getPathInfo(); if (!pathInfo.endsWith("/")) { pathInfo += "/"; } writer.println("<ul>"); for (File file : files) { writer.println("<li><a href=\"" + req.getContextPath() + req.getServletPath() + pathInfo + file.getName() + "\">" + file.getName() + "</a></li>"); } writer.println("</ul>"); } else { writer.println("<h4>NONE</h4>"); } writer.println("</body></html>"); } } catch (Exception e) { throw new ServletException("Cannot get downloads listing", e); } } private boolean isForbiddenPath(String requestedPath, HttpServletResponse resp) throws IOException { boolean forbidden = true; // assume it is forbidden if (requestedPath.toString().contains("rhq-agent")) { resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Use /agentupdate/download to obtain the agent"); } else if (requestedPath.toString().contains("rhq-client")) { resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Use /client/download to obtain the client"); } else { forbidden = false; } return forbidden; } private void sendErrorServerNotAcceptingRequests(HttpServletResponse resp) throws IOException { disableBrowserCache(resp); resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Server Is Down For Maintenance"); } private void disableBrowserCache(HttpServletResponse resp) { resp.setHeader("Cache-Control", "no-cache, no-store"); resp.setHeader("Expires", "-1"); resp.setHeader("Pragma", "no-cache"); } private List<File> getDownloadFiles(String requestedDirectory) throws Exception { // its possible if more than one root dir has the file, we'll get duplicates. // this should never happen, so ignore this edge case. List<File> returnFiles = new ArrayList<File>(); File[] rootDownloadDirs = getRootDownloadsDirs(); for (File rootDownloadDir : rootDownloadDirs) { File dir = new File(rootDownloadDir, requestedDirectory); File[] filesArray = dir.listFiles(); // this is simple - we only serve up files located in the requested directory - no content from subdirectories if (filesArray != null) { for (File file : filesArray) { if (file.isFile()) { returnFiles.add(file); } } } } return returnFiles; } /** * There are two locations for downloads - the static content under the EAR's rhq-downloads * and dynamic content in the data directory. * * @return the two root locations * * @throws Exception */ private File[] getRootDownloadsDirs() throws Exception { File earDir = LookupUtil.getCoreServer().getEarDeploymentDir(); File downloadDir = new File(earDir, "rhq-downloads"); if (!downloadDir.exists()) { throw new FileNotFoundException("Missing downloads directory at [" + downloadDir + "]"); } File dataDir = LookupUtil.getCoreServer().getJBossServerDataDir(); File dataDownloadDir = new File(dataDir, "rhq-downloads"); return new File[] { dataDownloadDir, downloadDir }; // put the data dir first, I think that should take precedence } private boolean isServerAcceptingRequests() { try { OperationMode mode = LookupUtil.getServerManager().getServer().getOperationMode(); return mode == OperationMode.NORMAL; } catch (Exception e) { return false; } } }