| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sling.servlets.get.impl.helpers; |
| |
| import static javax.servlet.http.HttpServletResponse.SC_NOT_MODIFIED; |
| import static org.apache.sling.api.servlets.HttpConstants.HEADER_IF_MODIFIED_SINCE; |
| import static org.apache.sling.api.servlets.HttpConstants.HEADER_LAST_MODIFIED; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.util.Date; |
| import java.util.Iterator; |
| |
| import javax.servlet.RequestDispatcher; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.sling.api.SlingConstants; |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.SlingHttpServletResponse; |
| import org.apache.sling.api.request.RequestDispatcherOptions; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceMetadata; |
| import org.apache.sling.api.resource.ResourceNotFoundException; |
| import org.apache.sling.api.resource.ResourceResolver; |
| import org.apache.sling.api.resource.ResourceUtil; |
| import org.apache.sling.api.servlets.SlingSafeMethodsServlet; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * The <code>StreamRendererServlet</code> streams the current resource to the |
| * client on behalf of the |
| * {@link org.apache.sling.servlets.get.impl.DefaultGetServlet}. If the current |
| * resource cannot be streamed it is rendered using the |
| * {@link PlainTextRendererServlet}. |
| */ |
| public class StreamRendererServlet extends SlingSafeMethodsServlet { |
| |
| public static final String EXT_RES = "res"; |
| |
| private static final long serialVersionUID = -1L; |
| |
| /** default log */ |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private boolean index; |
| |
| private String[] indexFiles; |
| |
| public StreamRendererServlet(boolean index, String[] indexFiles) { |
| this.index = index; |
| this.indexFiles = indexFiles; |
| } |
| |
| @Override |
| protected void doGet(SlingHttpServletRequest request, |
| SlingHttpServletResponse response) throws ServletException, |
| IOException { |
| |
| // whether this servlet is called as of a request include |
| final boolean included = request.getAttribute(SlingConstants.ATTR_REQUEST_SERVLET) != null; |
| |
| // ensure no extension or "res" |
| String ext = request.getRequestPathInfo().getExtension(); |
| if (ext != null && !ext.equals(EXT_RES)) { |
| request.getRequestProgressTracker().log( |
| "StreamRendererServlet does not support for extension " + ext); |
| if (included || response.isCommitted()) { |
| log.error( |
| "StreamRendererServlet does not support for extension {}", |
| ext); |
| } else { |
| response.sendError(HttpServletResponse.SC_NOT_FOUND); |
| } |
| return; |
| } |
| |
| final Resource resource = request.getResource(); |
| if (ResourceUtil.isNonExistingResource(resource)) { |
| throw new ResourceNotFoundException("No data to render."); |
| } |
| |
| // trailing slash on url means directory listing |
| if ("/".equals(request.getRequestPathInfo().getSuffix())) { |
| renderDirectory(request, response, included); |
| return; |
| } |
| |
| // check the last modification time and If-Modified-Since header |
| if (!included) { |
| ResourceMetadata meta = resource.getResourceMetadata(); |
| long modifTime = meta.getModificationTime(); |
| if (unmodified(request, modifTime)) { |
| response.setStatus(SC_NOT_MODIFIED); |
| return; |
| } |
| } |
| |
| // fall back to plain text rendering if the resource has no stream |
| InputStream stream = resource.adaptTo(InputStream.class); |
| if (stream != null) { |
| |
| streamResource(resource, stream, included, response); |
| |
| } else { |
| |
| // the resource is the root, do not redirect, immediately index |
| if ("/".equals(resource.getPath())) { |
| |
| renderDirectory(request, response, included); |
| |
| } else if (included || response.isCommitted() ) { |
| |
| // request is included or committed, not redirecting |
| request.getRequestProgressTracker().log( |
| "StreamRendererServlet: Not redirecting with trailing slash, response is committed or request included"); |
| log.warn("StreamRendererServlet: Not redirecting with trailing slash, response is committed or request included"); |
| |
| } else { |
| |
| // redirect to this with trailing slash to render the index |
| String url = request.getResourceResolver().map(request, |
| resource.getPath()) |
| + "/"; |
| response.sendRedirect(url); |
| |
| } |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if the request has a |
| * <code>If-Modified-Since</code> header whose date value is later than the |
| * last modification time given as <code>modifTime</code>. |
| * |
| * @param request The <code>ComponentRequest</code> checked for the |
| * <code>If-Modified-Since</code> header. |
| * @param modifTime The last modification time to compare the header to. |
| * @return <code>true</code> if the <code>modifTime</code> is less than or |
| * equal to the time of the <code>If-Modified-Since</code> header. |
| */ |
| private boolean unmodified(HttpServletRequest request, long modifTime) { |
| if (modifTime > 0) { |
| long modTime = modifTime / 1000; // seconds |
| long ims = request.getDateHeader(HEADER_IF_MODIFIED_SINCE) / 1000; |
| return modTime <= ims; |
| } |
| |
| // we have no modification time value, assume modified |
| return false; |
| } |
| |
| private void streamResource(final Resource resource, |
| final InputStream stream, final boolean included, |
| final SlingHttpServletResponse response) throws IOException { |
| // finally stream the resource |
| try { |
| |
| // set various response headers, unless the request is included |
| if (!included) { |
| setHeaders(resource, response); |
| } |
| |
| OutputStream out = response.getOutputStream(); |
| |
| byte[] buf = new byte[1024]; |
| int rd; |
| while ((rd = stream.read(buf)) >= 0) { |
| out.write(buf, 0, rd); |
| } |
| |
| } finally { |
| try { |
| stream.close(); |
| } catch (IOException ignore) { |
| // don't care |
| } |
| } |
| } |
| |
| private void renderDirectory(final SlingHttpServletRequest request, |
| final SlingHttpServletResponse response, final boolean included) |
| throws ServletException, IOException { |
| |
| // request is included or committed, not rendering index |
| if (included || response.isCommitted()) { |
| request.getRequestProgressTracker().log( |
| "StreamRendererServlet: Not rendering index, response is committed or request included"); |
| log.warn("StreamRendererServlet: Not rendering index, response is committed or request included"); |
| return; |
| } |
| |
| Resource resource = request.getResource(); |
| ResourceResolver resolver = request.getResourceResolver(); |
| |
| // check for an index file |
| for (String index : indexFiles) { |
| Resource fileRes = resolver.getResource(resource, index); |
| if (fileRes != null && !ResourceUtil.isSyntheticResource(fileRes)) { |
| |
| // include the index resource with no suffix and selectors ! |
| RequestDispatcherOptions rdo = new RequestDispatcherOptions(); |
| rdo.setReplaceSuffix(""); |
| rdo.setReplaceSelectors(""); |
| |
| RequestDispatcher dispatcher; |
| if (index.indexOf('.') < 0) { |
| String filePath = fileRes.getPath() + ".html"; |
| dispatcher = request.getRequestDispatcher(filePath, rdo); |
| } else { |
| dispatcher = request.getRequestDispatcher(fileRes, rdo); |
| } |
| |
| setHeaders(fileRes, response); |
| |
| dispatcher.include(request, response); |
| return; |
| } |
| } |
| |
| if (index) { |
| renderIndex(resource, response); |
| } else { |
| response.sendError(HttpServletResponse.SC_FORBIDDEN); |
| } |
| |
| } |
| |
| /** |
| * @param resource |
| * @param request |
| * @param response |
| */ |
| private void setHeaders(Resource resource, |
| SlingHttpServletResponse response) { |
| |
| final ResourceMetadata meta = resource.getResourceMetadata(); |
| final long modifTime = meta.getModificationTime(); |
| if (modifTime > 0) { |
| response.setDateHeader(HEADER_LAST_MODIFIED, modifTime); |
| } |
| |
| final String defaultContentType = "application/octet-stream"; |
| String contentType = meta.getContentType(); |
| if (contentType == null || defaultContentType.equals(contentType)) { |
| // if repository doesn't provide a content-type, or |
| // provides the |
| // default one, |
| // try to do better using our servlet context |
| final String ct = getServletContext().getMimeType( |
| resource.getPath()); |
| if (ct != null) { |
| contentType = ct; |
| } |
| } |
| if (contentType != null) { |
| response.setContentType(contentType); |
| } |
| |
| String encoding = meta.getCharacterEncoding(); |
| if (encoding != null) { |
| response.setCharacterEncoding(encoding); |
| } |
| |
| long length = meta.getContentLength(); |
| if (length > 0 && length < Integer.MAX_VALUE) { |
| response.setContentLength((int) length); |
| } |
| |
| } |
| |
| private void renderIndex(Resource resource, |
| SlingHttpServletResponse response) throws IOException { |
| |
| response.setContentType("text/html"); |
| response.setCharacterEncoding("UTF-8"); |
| |
| String path = resource.getPath(); |
| |
| PrintWriter pw = response.getWriter(); |
| pw.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">"); |
| pw.println("<html>"); |
| pw.println("<head>"); |
| pw.println("<title>Index of " + path + "</title>"); |
| pw.println("</head>"); |
| |
| pw.println("<body>"); |
| pw.println("<h1>Index of " + path + "</h1>"); |
| |
| pw.println("<pre>"); |
| pw.println("Name Last modified Size Description"); |
| pw.println("<hr>"); |
| |
| if (!"/".equals(path)) { |
| pw.println("<a href='../'>../</a> - Parent"); |
| } |
| |
| // render the children |
| Iterator<Resource> children = ResourceUtil.listChildren(resource); |
| while (children.hasNext()) { |
| renderChild(pw, children.next()); |
| } |
| |
| pw.println("</pre>"); |
| pw.println("</body>"); |
| pw.println("</html>"); |
| |
| } |
| |
| private void renderChild(PrintWriter pw, Resource resource) { |
| |
| String name = ResourceUtil.getName(resource.getPath()); |
| |
| InputStream ins = resource.adaptTo(InputStream.class); |
| if (ins == null) { |
| name += "/"; |
| } else { |
| try { |
| ins.close(); |
| } catch (IOException ignore) { |
| } |
| } |
| |
| String displayName = name; |
| String suffix; |
| if (displayName.length() >= 32) { |
| displayName = displayName.substring(0, 29) + "..."; |
| suffix = ""; |
| } else { |
| suffix = " ".substring( |
| 0, 32 - displayName.length()); |
| } |
| pw.printf("<a href='%s'>%s</a>%s", name, displayName, suffix); |
| |
| ResourceMetadata meta = resource.getResourceMetadata(); |
| long lastModified = meta.getModificationTime(); |
| pw.print(" " + new Date(lastModified) + " "); |
| |
| long length = meta.getContentLength(); |
| if (length > 0) { |
| pw.print(length); |
| } else { |
| pw.print('-'); |
| } |
| |
| pw.println(); |
| } |
| } |