blob: 23f6f717beb6b44d82ef5ce56719077e54f485f4 [file] [log] [blame]
/*
* 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();
}
}