package nl.gertontenham.magnolia.templating.servlets; import com.google.common.net.HttpHeaders; import info.magnolia.cms.filters.SelfMappingServlet; import info.magnolia.cms.util.ClasspathResourcesUtil; import info.magnolia.module.resources.ResourceLinker; import info.magnolia.resourceloader.Resource; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; /** * Servlet endpoint for any web resource under the default path <code>/static-resources</code> or path configured in servlet * parameter "resourcesRoot". * Note: This ia a copy of the ResourcesServlet in Magnolia 5.4 as this Servlet is hard to extend with its private properties and * protected methods. */ public class StaticResourcesServlet extends HttpServlet implements SelfMappingServlet { private static final Logger log = LoggerFactory.getLogger(StaticResourcesServlet.class); /** * Default root directory for resources streamed from the classpath. Resources folder is configurable via the servlet <code>resourcesRoot</code> init parameter. */ public static final String DEFAULT_RESOURCES_ROOT = "/magnolia-templating-foundation/static-resources"; protected String resourcesRoot; protected String requestedResourcePath; private final ResourceLinker linker; @Inject public StaticResourcesServlet(ResourceLinker linker) { this.linker = linker; } /** * @see javax.servlet.GenericServlet#init() */ @Override public void init() throws ServletException { super.init(); resourcesRoot = StringUtils.defaultIfEmpty(getInitParameter("resourcesRoot"), DEFAULT_RESOURCES_ROOT); //test if the folder is really there, else log warning and fall back to default. URL url = ClasspathResourcesUtil.getResource(resourcesRoot); log.debug("resources root is {}", resourcesRoot); if(url == null) { log.warn("Resource classpath root {} does not seem to exist. Some resources might not be available, please check your configuration. Falling back to default resources root {}", resourcesRoot, DEFAULT_RESOURCES_ROOT); // in case of misconfiguration, this should mitigate the risk of ending up with an unusable Magnolia instance. resourcesRoot = DEFAULT_RESOURCES_ROOT; } } @Override public String getSelfMappingPath() { return linker.getServletMapping(); } /** * All static resource get requests are handled here. * @throws java.io.IOException for error in accessing the resource or the servlet output stream */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { requestedResourcePath = getResourcePathFromRequest(request); if (StringUtils.isBlank(requestedResourcePath)) { log.warn("Invalid resource request : {} ", request.getRequestURI()); response.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } final Resource resource = linker.getResource(requestedResourcePath); if (resource == null) { log.debug("Requested resource not found for path {}", requestedResourcePath); response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } if (resource.isDirectory()) { log.warn("Invalid resource request: {} is a directory", request.getRequestURI()); // send 404 instead to avoid disclosing resource existence ? response.sendError(HttpServletResponse.SC_BAD_REQUEST); return; } serveResource(response, resource); } @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } public void serveResource(HttpServletResponse response, Resource resource) throws IOException { response.setDateHeader(HttpHeaders.LAST_MODIFIED, resource.getLastModified()); try (InputStream in = resource.openStream(); OutputStream out = response.getOutputStream()) { IOUtils.copy(in, out); out.flush(); } catch (IOException e) { log.debug("Can't load resource {} : {}", resource, e, e); response.sendError(HttpServletResponse.SC_NOT_FOUND); // tomcat usually throws a ClientAbortException anytime the user stop loading the page log.debug("Unable to serve resource due to a {} exception: ", e, e); if (!response.isCommitted()) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } } public String getResourcePathFromRequest(HttpServletRequest request) { // handle includes String resourcePath = (String) request.getAttribute("javax.servlet.include.path_info"); // handle forwards if (resourcePath == null) { resourcePath = (String) request.getAttribute("javax.servlet.forward.path_info"); } // standard request if (resourcePath == null) { resourcePath = request.getPathInfo(); } return resourcesRoot + resourcePath; } public ResourceLinker getLinker() { return linker; } }