io.wcm.handler.media.impl.AbstractMediaFileServlet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of io.wcm.handler.media Show documentation
Show all versions of io.wcm.handler.media Show documentation
Media resolving, processing and markup generation.
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2014 wcm.io
* %%
* Licensed 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.
* #L%
*/
package io.wcm.handler.media.impl;
import static io.wcm.handler.media.impl.MediaFileServletConstants.HEADER_CONTENT_DISPOSITION;
import static io.wcm.handler.media.impl.MediaFileServletConstants.SELECTOR_DOWNLOAD;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.wcm.sling.commons.request.RequestPath;
import io.wcm.wcm.commons.caching.CacheHeader;
import io.wcm.wcm.commons.contenttype.ContentType;
/**
* Stream binary data stored in a nt:file or nt:resource node.
* Optional support for Content-Disposition header ("download_attachment").
*/
abstract class AbstractMediaFileServlet extends SlingSafeMethodsServlet {
private static final long serialVersionUID = 1L;
@Override
@SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException {
// get binary data resource
Resource resource = getBinaryDataResource(request);
if (resource == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
// check if the resource was modified since last request
if (isNotModified(resource, request, response)) {
return;
}
// get binary data and send to client
byte[] binaryData = getBinaryData(resource, request);
if (binaryData == null || binaryData.length == 0) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
else {
String contentType = getContentType(resource, request);
sendBinaryData(binaryData, contentType, request, response);
}
}
/**
* Get resource containing the binary data to deliver.
* @param request Request
* @return Resource pointing to nt:file or nt:resource node
*/
protected @Nullable Resource getBinaryDataResource(SlingHttpServletRequest request) {
return request.getResource();
}
/**
* Checks if the resource was modified since last request
* @param resource Resource pointing to nt:file or nt:resource node
* @param request Request
* @param response Response
* @return true if the resource is not modified and should not be delivered anew
*/
protected boolean isNotModified(@NotNull Resource resource, @NotNull SlingHttpServletRequest request,
@NotNull SlingHttpServletResponse response) {
// check resource's modification date against the If-Modified-Since header and send 304 if resource wasn't modified
// never send expires header on author or publish instance (performance optimization - if medialib items changes
// users have to refresh browsers cache)
return CacheHeader.isNotModified(resource, request, response, false);
}
/**
* Get binary data from the referenced nt:file or nt:resourcer resource.
* @param resource Resource
* @return Binary data or null if not binary data found
*/
protected byte @Nullable [] getBinaryData(@NotNull Resource resource,
@SuppressWarnings({ "unused", "java:S1172" }) @NotNull SlingHttpServletRequest request) throws IOException {
InputStream is = resource.adaptTo(InputStream.class);
if (is == null) {
return null;
}
try {
return IOUtils.toByteArray(is);
}
finally {
is.close();
}
}
/**
* Get content type from the reference nt:file or nt:resourcer resource.
* @param resource Resource
* @return Content type (never null)
*/
protected @NotNull String getContentType(@NotNull Resource resource,
@SuppressWarnings({ "unused", "java:S1172" }) @NotNull SlingHttpServletRequest request) {
String mimeType = JcrBinary.getMimeType(resource);
if (StringUtils.isEmpty(mimeType)) {
mimeType = ContentType.OCTET_STREAM;
}
return mimeType;
}
/**
* Send binary data to output stream. Respect optional content disposition header handling.
* @param binaryData Binary data array.
* @param contentType Content type
* @param request Request
* @param response Response
*/
protected void sendBinaryData(byte @NotNull [] binaryData, @NotNull String contentType,
@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException {
// set content type and length
response.setContentType(contentType);
response.setContentLength(binaryData.length);
// Handling of the "force download" selector
if (RequestPath.hasSelector(request, SELECTOR_DOWNLOAD)) {
// Overwrite MIME type with one suited for downloads
response.setContentType(ContentType.DOWNLOAD);
// set content disposition header to file name from suffix
setContentDispositionAttachmentHeader(request, response);
}
// special handling for SVG images which are not treated as download:
// force content disposition header to prevent stored XSS attack with malicious JavaScript in SVG file
else if (StringUtils.equals(contentType, ContentType.SVG)) {
setContentDispositionAttachmentHeader(request, response);
}
// write binary data
OutputStream out = response.getOutputStream();
out.write(binaryData);
out.flush();
}
private void setContentDispositionAttachmentHeader(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) {
// Construct disposition header
StringBuilder dispositionHeader = new StringBuilder("attachment;");
String suffix = request.getRequestPathInfo().getSuffix();
suffix = StringUtils.stripStart(suffix, "/");
if (StringUtils.isNotEmpty(suffix)) {
dispositionHeader.append("filename=\"").append(suffix).append('\"');
}
response.setHeader(HEADER_CONTENT_DISPOSITION, dispositionHeader.toString());
}
}