blob: ab47cf55df370319224884def369574dbfb346fb [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.engine.impl;
import javax.servlet.DispatcherType;
import javax.servlet.FilterChain;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.SocketException;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.SlingServletException;
import org.apache.sling.api.adapter.AdapterManager;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceNotFoundException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.ErrorHandler;
import org.apache.sling.api.servlets.ServletResolver;
import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;
import org.apache.sling.commons.mime.MimeTypeService;
import org.apache.sling.engine.SlingRequestProcessor;
import org.apache.sling.engine.impl.debug.RequestInfoProviderImpl;
import org.apache.sling.engine.impl.filter.ErrorFilterChain;
import org.apache.sling.engine.impl.filter.FilterHandle;
import org.apache.sling.engine.impl.filter.RequestSlingFilterChain;
import org.apache.sling.engine.impl.filter.ServletFilterManager;
import org.apache.sling.engine.impl.filter.ServletFilterManager.FilterChainType;
import org.apache.sling.engine.impl.filter.SlingComponentFilterChain;
import org.apache.sling.engine.impl.helper.SlingServletContext;
import org.apache.sling.engine.impl.parameters.ParameterSupport;
import org.apache.sling.engine.impl.request.ContentData;
import org.apache.sling.engine.impl.request.DispatchingInfo;
import org.apache.sling.engine.impl.request.RequestData;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.sling.api.SlingConstants.ERROR_SERVLET_NAME;
@Component(
service = {SlingRequestProcessor.class, SlingRequestProcessorImpl.class},
configurationPid = Config.PID)
public class SlingRequestProcessorImpl implements SlingRequestProcessor {
/** default log */
private final Logger log = LoggerFactory.getLogger(SlingRequestProcessorImpl.class);
@Reference
private volatile ServletResolver servletResolver;
@Reference
private volatile ServletFilterManager filterManager;
@Reference
private volatile RequestProcessorMBeanImpl mbean;
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
private volatile AdapterManager adapterManager;
/**
* Resolves MIME types
*
* @see #getMimeType(String)
*/
@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL)
private volatile MimeTypeService mimeTypeService;
private final DefaultErrorHandler errorHandler = new DefaultErrorHandler();
private volatile int maxCallCounter = Config.DEFAULT_MAX_CALL_COUNTER;
private volatile int maxInclusionCounter = Config.DEFAULT_MAX_INCLUSION_COUNTER;
private volatile List<StaticResponseHeader> additionalResponseHeaders = Collections.emptyList();
private volatile boolean protectHeadersOnInclude;
private volatile boolean checkContentTypeOnInclude;
private volatile boolean disableCheckCompliantGetUserPrincipal;
@Activate
public void activate(final Config config) {
this.modified(config);
}
@Modified
public void modified(final Config config) {
final List<StaticResponseHeader> mappings = new ArrayList<>();
final String[] props = config.sling_additional_response_headers();
if (props != null) {
for (final String prop : props) {
if (prop != null && prop.trim().length() > 0) {
try {
final StaticResponseHeader mapping = new StaticResponseHeader(prop.trim());
mappings.add(mapping);
} catch (final IllegalArgumentException iae) {
log.info("configure: Ignoring '{}': {}", prop, iae.getMessage());
}
}
}
}
this.additionalResponseHeaders = mappings;
// configure the request limits
this.maxInclusionCounter = config.sling_max_inclusions();
this.maxCallCounter = config.sling_max_calls();
this.protectHeadersOnInclude = config.sling_includes_protectheaders();
this.checkContentTypeOnInclude = config.sling_includes_checkcontenttype();
this.disableCheckCompliantGetUserPrincipal = config.disable_spec_compliant_getuserprincipal();
}
@Reference(target = SlingServletContext.TARGET, policy = ReferencePolicy.DYNAMIC, updated = "bindServletContext")
void bindServletContext(final ServletContext servletContext) {
this.errorHandler.setServerInfo(servletContext.getServerInfo());
}
void unbindServletContext(final ServletContext servletContext) {
// nothing to do here, but DS requires this method
}
@Reference(
name = "ErrorHandler",
cardinality = ReferenceCardinality.OPTIONAL,
policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY)
void setErrorHandler(final ErrorHandler handler) {
this.errorHandler.setDelegate(handler);
}
void unsetErrorHandler(final ErrorHandler handler) {
this.errorHandler.setDelegate(null);
}
public int getMaxCallCounter() {
return maxCallCounter;
}
public int getMaxIncludeCounter() {
return maxInclusionCounter;
}
public List<StaticResponseHeader> getAdditionalResponseHeaders() {
return this.additionalResponseHeaders;
}
public <Type> Type adaptTo(Object object, Class<Type> type) {
final AdapterManager adapterManager = this.adapterManager;
if (adapterManager != null) {
return adapterManager.getAdapter(object, type);
}
// no adapter manager, nothing to adapt to
return null;
}
/**
* This method is directly called by the Sling main servlet.
*/
public void doProcessRequest(
final HttpServletRequest servletRequest,
final HttpServletResponse servletResponse,
final ResourceResolver resourceResolver)
throws IOException {
final ServletResolver sr = this.servletResolver;
// check that we have all required services
if (resourceResolver == null || sr == null) {
// Dependencies are missing
// In this case we must not use the Sling error handling infrastructure but
// just return a 503 status response handled by the servlet container environment
final int status = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
String errorMessage = "Required service missing (";
if (resourceResolver == null) {
errorMessage = errorMessage.concat("ResourceResolver");
if (sr == null) {
errorMessage = errorMessage.concat(", ");
}
}
if (sr == null) {
errorMessage = errorMessage.concat("ServletResolver");
}
log.debug("{}, cannot service requests, sending status {}", errorMessage, status);
servletResponse.sendError(status, errorMessage);
return;
}
// setting the Sling request and response
final RequestData requestData = new RequestData(
this,
servletRequest,
servletResponse,
protectHeadersOnInclude,
checkContentTypeOnInclude,
this.disableCheckCompliantGetUserPrincipal);
final SlingHttpServletRequest request = requestData.getSlingRequest();
final SlingHttpServletResponse response = requestData.getSlingResponse();
try {
// initialize the request data - resolve resource and servlet
final Resource resource = requestData.initResource(resourceResolver);
requestData.initServlet(resource, sr);
final FilterHandle[] filters = filterManager.getFilters(FilterChainType.REQUEST);
final FilterChain processor = new RequestSlingFilterChain(this, filters);
request.getRequestProgressTracker()
.log("Applying ".concat(FilterChainType.REQUEST.name()).concat("filters"));
processor.doFilter(request, response);
} catch (final SlingHttpServletResponseImpl.WriterAlreadyClosedException wace) {
// this is an exception case, log as error
log.error("Writer has already been closed.", wace);
} catch (final ResourceNotFoundException rnfe) {
// send this exception as a 404 status
log.debug("service: Resource {} not found", rnfe.getResource());
handleError(HttpServletResponse.SC_NOT_FOUND, rnfe.getMessage(), request, response);
} catch (final SlingException se) {
// send this exception as is (albeit unwrapping and wrapped
// exception.
Throwable t = se;
while (t instanceof SlingException && t.getCause() != null) {
t = t.getCause();
}
handleError(requestData, "SlingException", t, request, response);
} catch (final AccessControlException ace) {
// SLING-319 if anything goes wrong, send 403/FORBIDDEN
log.debug(
"service: Authenticated user {} does not have enough rights to executed requested action",
request.getRemoteUser());
handleError(HttpServletResponse.SC_FORBIDDEN, null, request, response);
} catch (final FileNotFoundException fnfe) {
// send this exception as a 404 status
log.debug("service: File not found: {}", fnfe.getMessage());
handleError(HttpServletResponse.SC_NOT_FOUND, fnfe.getMessage(), request, response);
} catch (final IOException ioe) {
// unwrap any causes (Jetty wraps SocketException in EofException)
Throwable cause = ioe;
while (cause.getCause() != null) {
cause = cause.getCause();
}
if (cause instanceof SocketException) {
// if the cause is a SocketException, the client most
// probably aborted the request, we do not fill the log with errors
log.debug("service: Socketexception (Client abort or network problem", ioe);
} else {
// otherwise call error handler with original exception
handleError(requestData, "IOException", ioe, request, response);
}
} catch (final Throwable t) {
handleError(requestData, "Throwable", t, request, response);
} finally {
// record the request for the web console and info provider
RequestInfoProviderImpl.recordRequest(request);
final RequestProcessorMBeanImpl localBean = this.mbean;
if (localBean != null) {
localBean.addRequestData(requestData);
}
}
}
private void handleError(
final RequestData requestData,
final String identifier,
final Throwable throwable,
final SlingHttpServletRequest request,
final SlingHttpServletResponse response)
throws IOException {
// we assume, that this is the name of the causing servlet
if (requestData.getActiveServletName() != null) {
request.setAttribute(ERROR_SERVLET_NAME, requestData.getActiveServletName());
}
log.error("service: Uncaught {}", identifier, throwable);
handleError(throwable, request, response);
}
// ---------- SlingRequestProcessor interface
/**
* @see org.apache.sling.engine.SlingRequestProcessor#processRequest(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.apache.sling.api.resource.ResourceResolver)
*/
@Override
public void processRequest(
final HttpServletRequest servletRequest,
final HttpServletResponse servletResponse,
final ResourceResolver resourceResolver)
throws IOException {
// set the marker for the parameter support
final Object oldValue = servletRequest.getAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING);
servletRequest.setAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING, Boolean.TRUE);
try {
this.doProcessRequest(servletRequest, servletResponse, resourceResolver);
} finally {
// restore the old value
if (oldValue != null) {
servletRequest.setAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING, oldValue);
} else {
servletRequest.removeAttribute(ParameterSupport.MARKER_IS_SERVICE_PROCESSING);
}
}
}
/**
* Renders the component defined by the RequestData's current ComponentData
* instance after calling all filters of the given
* {@link org.apache.sling.engine.impl.filter.ServletFilterManager.FilterChainType
* filterChainType}.
*
* @param request
* @param response
* @param filterChainType
* @throws IOException
* @throws ServletException
*/
public void processComponent(
SlingHttpServletRequest request, SlingHttpServletResponse response, final FilterChainType filterChainType)
throws IOException, ServletException {
final FilterHandle filters[] = filterManager.getFilters(filterChainType);
FilterChain processor = new SlingComponentFilterChain(filters);
request.getRequestProgressTracker().log("Applying " + filterChainType + "filters");
processor.doFilter(request, response);
}
// ---------- Generic Content Request processor ----------------------------
/**
* Dispatches the request on behalf of the
* {@link org.apache.sling.engine.impl.request.SlingRequestDispatcher}.
* @param request The request
* @param response The response
* @param resource The resource
* @param resolvedURL Request path info
* @param DispatchingInfo dispatching info
*/
public void dispatchRequest(
final ServletRequest request,
final ServletResponse response,
final Resource resource,
final RequestPathInfo resolvedURL,
final DispatchingInfo dispatchingInfo)
throws IOException, ServletException {
// we need a SlingHttpServletRequest/SlingHttpServletResponse tupel
// to continue
final SlingHttpServletRequest cRequest = RequestData.toSlingHttpServletRequest(request);
final SlingHttpServletResponse cResponse = RequestData.toSlingHttpServletResponse(response);
// check that we have all required services
final ServletResolver sr = this.servletResolver;
if (sr == null) {
final int status = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
String errorMessage = "Required service missing (ServletResolver)";
log.debug("{}, cannot dispatch requests, sending status {}", errorMessage, status);
cResponse.sendError(status, errorMessage);
return;
}
// get the request data (and btw check the correct type)
final RequestData requestData = RequestData.getRequestData(cRequest);
final ContentData oldContentData = requestData.getContentData();
final ContentData contentData = requestData.setContent(resource, resolvedURL);
final DispatchingInfo oldDispatchingInfo = requestData.getDispatchingInfo();
requestData.setDispatchingInfo(dispatchingInfo);
try {
// resolve the servlet
Servlet servlet = sr.resolveServlet(cRequest);
contentData.setServlet(servlet);
final FilterChainType type = dispatchingInfo.getType() == DispatcherType.INCLUDE
? FilterChainType.INCLUDE
: FilterChainType.FORWARD;
processComponent(cRequest, cResponse, type);
} finally {
requestData.resetContent(oldContentData);
requestData.setDispatchingInfo(oldDispatchingInfo);
}
}
// ---------- Error Handling with Filters
private void handleError(
final FilterChain errorFilterChain,
final SlingHttpServletRequest request,
final SlingHttpServletResponse response)
throws IOException {
request.getRequestProgressTracker().log("Applying " + FilterChainType.ERROR + " filters");
try {
// wrap the response ensuring getWriter will fall back to wrapping
// the response output stream if reset does not reset this
errorFilterChain.doFilter(request, new ErrorResponseWrapper(response));
} catch (final ServletException se) {
throw new SlingServletException(se);
}
}
void handleError(
final int status,
final String message,
final SlingHttpServletRequest request,
final SlingHttpServletResponse response)
throws IOException {
final FilterHandle[] filters = filterManager.getFilters(FilterChainType.ERROR);
final FilterChain processor = new ErrorFilterChain(filters, errorHandler, status, message);
this.handleError(processor, request, response);
}
private void handleError(
final Throwable throwable, final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws IOException {
final FilterHandle[] filters = filterManager.getFilters(FilterChainType.ERROR);
final FilterChain processor = new ErrorFilterChain(filters, errorHandler, throwable);
this.handleError(processor, request, response);
}
private static class ErrorResponseWrapper extends SlingHttpServletResponseWrapper {
private PrintWriter writer;
public ErrorResponseWrapper(SlingHttpServletResponse wrappedResponse) {
super(wrappedResponse);
}
@Override
public PrintWriter getWriter() throws IOException {
if (writer == null) {
try {
writer = super.getWriter();
} catch (IllegalStateException ise) {
// resetting the response did not reset the output channel
// status and we have to create a writer based on the output
// stream using the character encoding already set on the
// response, defaulting to ISO-8859-1
OutputStream out = getOutputStream();
String encoding = getCharacterEncoding();
if (encoding == null) {
encoding = "ISO-8859-1";
setCharacterEncoding(encoding);
}
Writer w = new OutputStreamWriter(out, encoding);
writer = new PrintWriter(w);
}
}
return writer;
}
/**
* Flush the writer if the {@link #getWriter()} method was called
* to potentially wrap an OuputStream still existing in the response.
*/
@Override
public void flushBuffer() throws IOException {
if (writer != null) {
writer.flush();
}
super.flushBuffer();
}
}
/**
* Returns the MIME type as resolved by the <code>MimeTypeService</code> or
* <code>null</code> if the service is not available.
*/
public String getMimeType(final String name) {
MimeTypeService mtservice = mimeTypeService;
if (mtservice != null) {
return mtservice.getMimeType(name);
}
log.debug("getMimeType: MimeTypeService not available, cannot resolve mime type for {}", name);
return null;
}
}