| /* |
| * 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); |
| } |
| } |
| } |
| |
| } |