blob: ba48b898be15a9e77eafa01c1b05280aa0f88195 [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.parameters;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.RequestContext;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.api.request.RequestParameterMap;
import org.apache.sling.api.resource.ResourceResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ParameterSupport {
/**
* The name of the form encoding request parameter indicating the character
* encoding of submitted request parameters. This request parameter
* overwrites any value of the {@code ServletRequest.getCharacterEncoding()}
* method (which unfortunately happens to be returning {@code null} most of
* the time.
*/
public final static String PARAMETER_FORMENCODING = "_charset_";
/**
* Request attribute which is set if the current request is "started"
* by calling {@link org.apache.sling.engine.SlingRequestProcessor#processRequest(HttpServletRequest, HttpServletResponse, ResourceResolver)}
* This marker is evaluated in {@link #getRequestParameterMapInternal()}.
*/
public final static String MARKER_IS_SERVICE_PROCESSING = ParameterSupport.class.getName() + "/ServiceProcessingMarker";
// name of the request attribute caching the ParameterSupport instance
// used during the request
private static final String ATTR_NAME = ParameterSupport.class.getName();
/** Content type signaling parameters in request body */
private static final String WWW_FORM_URL_ENC = "application/x-www-form-urlencoded";
/** name of the header used to identify an upload mode */
public static final String SLING_UPLOADMODE_HEADER = "Sling-uploadmode";
/** name of the parameter used to identify upload mode */
public static final String UPLOADMODE_PARAM = "uploadmode";
/** request attribute that stores the parts iterator when streaming */
public static final String REQUEST_PARTS_ITERATOR_ATTRIBUTE = "request-parts-iterator";
/** value of upload mode header/parameter indicating streaming is requested */
public static final String STREAM_UPLOAD = "stream";
/** default log */
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* The maximum size allowed for <tt>multipart/form-data</tt>
* requests
*
* <p>The default is <tt>-1L</tt>, which means unlimited.
*/
private static long maxRequestSize = -1L;
/**
* The directory location where files will be stored
*/
private static File location = null;
/**
* The maximum size allowed for uploaded files.
*
* <p>The default is <tt>-1L</tt>, which means unlimited.
*/
private static long maxFileSize = -1L;
/**
* The size threshold after which the file will be written to disk
*/
private static int fileSizeThreshold = 256000;
/**
* Check for additional parameters from the container.
* TODO - We can remove this once we move engine to Servlet 3.1
*/
private static boolean checkForAdditionalParameters = false;
private final HttpServletRequest servletRequest;
private ParameterMap postParameterMap;
private boolean requestDataUsed;
/**
* Returns the {@code ParameterSupport} instance supporting request
* parameter for the give {@code request}. For a single request only a
* single instance is actually used. This single instance is cached as a
* request attribute. If such an attribute already exists which is not an
* instance of this class, the request parameter is replaced.
*
* @param request The {@code HttpServletRequest} for which to return request
* parameter support.
* @return The {@code ParameterSupport} for the given request.
*/
public static ParameterSupport getInstance(HttpServletRequest request) {
Object instance = request.getAttribute(ATTR_NAME);
if (!(instance instanceof ParameterSupport)) {
instance = new ParameterSupport(request);
request.setAttribute(ATTR_NAME, instance);
}
return (ParameterSupport) instance;
}
/**
* Returns a {@code HttpServletRequestWrapper} which implements request
* parameter access backed by an instance of the {@code ParameterSupport}
* class.
* <p>
*
* @param request The {@code HttpServletRequest} to wrap
* @return The wrapped {@code request}
*/
public static HttpServletRequestWrapper getParameterSupportRequestWrapper(final HttpServletRequest request) {
return new ParameterSupportHttpServletRequestWrapper(request);
}
static void configure(final long maxRequestSize, final String location, final long maxFileSize,
final int fileSizeThreshold,
final boolean checkForAdditionalParameters) {
ParameterSupport.maxRequestSize = (maxRequestSize > 0) ? maxRequestSize : -1;
ParameterSupport.location = (location != null) ? new File(location) : null;
ParameterSupport.maxFileSize = (maxFileSize > 0) ? maxFileSize : -1;
ParameterSupport.fileSizeThreshold = (fileSizeThreshold > 0) ? fileSizeThreshold : 256000;
ParameterSupport.checkForAdditionalParameters = checkForAdditionalParameters;
}
private ParameterSupport(HttpServletRequest servletRequest) {
this.servletRequest = servletRequest;
}
private HttpServletRequest getServletRequest() {
return servletRequest;
}
public boolean requestDataUsed() {
return this.requestDataUsed;
}
public String getParameter(String name) {
return getRequestParameterMapInternal().getStringValue(name);
}
public String[] getParameterValues(String name) {
return getRequestParameterMapInternal().getStringValues(name);
}
public Map<String, String[]> getParameterMap() {
return getRequestParameterMapInternal().getStringParameterMap();
}
public Enumeration<String> getParameterNames() {
return new Enumeration<String>() {
private final Iterator<String> base = ParameterSupport.this.getRequestParameterMapInternal().keySet().iterator();
@Override
public boolean hasMoreElements() {
return this.base.hasNext();
}
@Override
public String nextElement() {
return this.base.next();
}
};
}
public RequestParameter getRequestParameter(String name) {
return getRequestParameterMapInternal().getValue(name);
}
public RequestParameter[] getRequestParameters(String name) {
return getRequestParameterMapInternal().getValues(name);
}
public Object getPart(String name) {
return getRequestParameterMapInternal().getPart(name);
}
public Collection<?> getParts() {
return getRequestParameterMapInternal().getParts();
}
public RequestParameterMap getRequestParameterMap() {
return getRequestParameterMapInternal();
}
public List<RequestParameter> getRequestParameterList() {
return getRequestParameterMapInternal().getRequestParameterList();
}
private ParameterMap getRequestParameterMapInternal() {
if (this.postParameterMap == null) {
// SLING-508 Try to force servlet container to decode parameters
// as ISO-8859-1 such that we can recode later
String encoding = getServletRequest().getCharacterEncoding();
if (encoding == null) {
encoding = Util.ENCODING_DIRECT;
try {
getServletRequest().setCharacterEncoding(encoding);
} catch (UnsupportedEncodingException uee) {
throw new SlingUnsupportedEncodingException(uee);
}
}
// SLING-152 Get parameters from the servlet Container
ParameterMap parameters = new ParameterMap();
// fallback is only used if this request has been started by a service call
boolean useFallback = getServletRequest().getAttribute(MARKER_IS_SERVICE_PROCESSING) != null;
boolean addContainerParameters = false;
// Query String
final String query = getServletRequest().getQueryString();
if (query != null) {
try {
InputStream input = Util.toInputStream(query);
Util.parseQueryString(input, encoding, parameters, false);
addContainerParameters = checkForAdditionalParameters;
} catch (IllegalArgumentException e) {
this.log.error("getRequestParameterMapInternal: Error parsing request", e);
} catch (UnsupportedEncodingException e) {
throw new SlingUnsupportedEncodingException(e);
} catch (IOException e) {
this.log.error("getRequestParameterMapInternal: Error parsing request", e);
}
useFallback = false;
} else {
addContainerParameters = checkForAdditionalParameters;
useFallback = true;
}
// POST requests
final boolean isPost = "POST".equals(this.getServletRequest().getMethod());
if (isPost) {
// WWW URL Form Encoded POST
if (isWWWFormEncodedContent(this.getServletRequest())) {
try {
InputStream input = this.getServletRequest().getInputStream();
Util.parseQueryString(input, encoding, parameters, false);
addContainerParameters = checkForAdditionalParameters;
} catch (IllegalArgumentException e) {
this.log.error("getRequestParameterMapInternal: Error parsing request", e);
} catch (UnsupportedEncodingException e) {
throw new SlingUnsupportedEncodingException(e);
} catch (IOException e) {
this.log.error("getRequestParameterMapInternal: Error parsing request", e);
}
this.requestDataUsed = true;
useFallback = false;
}
// Multipart POST
if (ServletFileUpload.isMultipartContent(new ServletRequestContext(this.getServletRequest()))) {
if (isStreamed(parameters, this.getServletRequest())) {
// special case, the request is Multipart and streamed processing has been requested
try {
this.getServletRequest().setAttribute(REQUEST_PARTS_ITERATOR_ATTRIBUTE, new RequestPartsIterator(this.getServletRequest()));
this.log.debug("getRequestParameterMapInternal: Iterator<javax.servlet.http.Part> available as request attribute named request-parts-iterator");
} catch (IOException e) {
this.log.error("getRequestParameterMapInternal: Error parsing multipart streamed request", e);
} catch (FileUploadException e) {
this.log.error("getRequestParameterMapInternal: Error parsing multipart streamed request", e);
}
// The request data has been passed to the RequestPartsIterator, hence from a RequestParameter pov its been used, and must not be used again.
this.requestDataUsed = true;
// must not try and get anything from the request at this point so avoid jumping through the stream.
addContainerParameters = false;
useFallback = false;
} else {
this.parseMultiPartPost(parameters);
this.requestDataUsed = true;
addContainerParameters = checkForAdditionalParameters;
useFallback = false;
}
}
}
if ( useFallback ) {
getContainerParameters(parameters, encoding, true);
} else if ( addContainerParameters ) {
getContainerParameters(parameters, encoding, false);
}
// apply any form encoding (from '_charset_') in the parameter map
Util.fixEncoding(parameters);
this.postParameterMap = parameters;
}
return this.postParameterMap;
}
/**
* Checks to see if there is an upload mode header or uploadmode parameter indicating the request is
* to be streamed from the client to the server.
* @param parameters parameters processed from the query string only.
* @param servletRequest the servlet request, where the body has not been processed.
* @return true if the request was made with streaming in mind.
*/
private boolean isStreamed(ParameterMap parameters, HttpServletRequest servletRequest) {
if ( STREAM_UPLOAD.equals(servletRequest.getHeader(SLING_UPLOADMODE_HEADER)) ) {
return true;
}
RequestParameter[] rp = parameters.get(UPLOADMODE_PARAM);
return ( rp != null && rp.length == 1 && STREAM_UPLOAD.equals(rp[0].getString()));
}
private void getContainerParameters(final ParameterMap parameters, final String encoding, final boolean alwaysAdd) {
final Map<?, ?> pMap = getServletRequest().getParameterMap();
for (Map.Entry<?, ?> entry : pMap.entrySet()) {
final String name = (String) entry.getKey();
if ( alwaysAdd || !parameters.containsKey(name) ) {
final String[] values = (String[]) entry.getValue();
for (int i = 0; i < values.length; i++) {
parameters.addParameter(new ContainerRequestParameter(
name, values[i], encoding), false);
}
}
}
}
private static final boolean isWWWFormEncodedContent(HttpServletRequest request) {
String contentType = request.getContentType();
if (contentType == null) {
return false;
}
// This check assumes the content type ends after the WWW_FORM_URL_ENC
// or continues with blank or semicolon. It will probably break if
// the content type is some string extension of WWW_FORM_URL_ENC
// such as "application/x-www-form-urlencoded-bla"
if (contentType.toLowerCase(Locale.ENGLISH).startsWith(WWW_FORM_URL_ENC)) {
return true;
}
return false;
}
private void parseMultiPartPost(ParameterMap parameters) {
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload();
upload.setSizeMax(ParameterSupport.maxRequestSize);
upload.setFileSizeMax(ParameterSupport.maxFileSize);
upload.setFileItemFactory(new DiskFileItemFactory(ParameterSupport.fileSizeThreshold,
ParameterSupport.location));
RequestContext rc = new ServletRequestContext(this.getServletRequest()) {
@Override
public String getCharacterEncoding() {
String enc = super.getCharacterEncoding();
return (enc != null) ? enc : Util.ENCODING_DIRECT;
}
};
// Parse the request
List<?> /* FileItem */items = null;
try {
items = upload.parseRequest(rc);
} catch (FileUploadException fue) {
this.log.error("parseMultiPartPost: Error parsing request", fue);
}
if (items != null && items.size() > 0) {
for (Iterator<?> ii = items.iterator(); ii.hasNext();) {
FileItem fileItem = (FileItem) ii.next();
RequestParameter pp = new MultipartRequestParameter(fileItem);
parameters.addParameter(pp, false);
}
}
}
}