/*
* Password Management Servlets (PWM)
* http://www.pwm-project.org
*
* Copyright (c) 2006-2009 Novell, Inc.
* Copyright (c) 2009-2017 The PWM Project
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package password.pwm.http;
import org.apache.commons.io.IOUtils;
import password.pwm.AppProperty;
import password.pwm.PwmConstants;
import password.pwm.config.Configuration;
import password.pwm.error.ErrorInformation;
import password.pwm.error.PwmError;
import password.pwm.error.PwmUnrecoverableException;
import password.pwm.util.java.JsonUtil;
import password.pwm.util.PasswordData;
import password.pwm.util.java.StringUtil;
import password.pwm.util.Validator;
import password.pwm.util.logging.PwmLogger;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public abstract class PwmHttpRequestWrapper {
private static final PwmLogger LOGGER = PwmLogger.forClass(PwmHttpRequestWrapper.class);
private final HttpServletRequest httpServletRequest;
private final Configuration configuration;
public enum Flag {
BypassValidation
}
protected PwmHttpRequestWrapper(final HttpServletRequest request, final Configuration configuration) {
this.httpServletRequest = request;
this.configuration = configuration;
}
public HttpServletRequest getHttpServletRequest() {
return this.httpServletRequest;
}
public boolean isJsonRequest() {
final String acceptHeader = this.readHeaderValueAsString(HttpHeader.Accept);
return acceptHeader.contains(PwmConstants.AcceptValue.json.getHeaderValue());
}
public boolean isHtmlRequest() {
final String acceptHeader = this.readHeaderValueAsString(HttpHeader.Accept);
return acceptHeader.contains(PwmConstants.AcceptValue.html.getHeaderValue()) || acceptHeader.contains("*/*");
}
public String getContextPath() {
return httpServletRequest.getContextPath();
}
public String readRequestBodyAsString()
throws IOException, PwmUnrecoverableException {
final int maxChars = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_BODY_MAXREAD_LENGTH));
return readRequestBodyAsString(maxChars);
}
public String readRequestBodyAsString(final int maxChars)
throws IOException, PwmUnrecoverableException
{
final StringWriter stringWriter = new StringWriter();
final Reader readerStream = new InputStreamReader(
getHttpServletRequest().getInputStream(),
PwmConstants.DEFAULT_CHARSET
);
try {
IOUtils.copy(readerStream, stringWriter);
} catch (Exception e) {
final String errorMsg = "error reading request body stream: " + e.getMessage();
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,errorMsg));
} finally {
IOUtils.closeQuietly(readerStream);
}
final String stringValue = stringWriter.toString();
if (stringValue.length() > maxChars) {
throw new PwmUnrecoverableException(new ErrorInformation(PwmError.ERROR_UNKNOWN,"input request body is to big, size=" + stringValue.length() + ", max=" + maxChars));
}
return stringValue;
}
public Map<String, String> readBodyAsJsonStringMap(final Flag... flags)
throws IOException, PwmUnrecoverableException
{
final boolean bypassInputValidation = flags != null && Arrays.asList(flags).contains(Flag.BypassValidation);
final String bodyString = readRequestBodyAsString();
final Map<String, String> inputMap = JsonUtil.deserializeStringMap(bodyString);
final boolean trim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_TRIM));
final boolean passwordTrim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_PASSWORD_TRIM));
final int maxLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final Map<String, String> outputMap = new LinkedHashMap<>();
if (inputMap != null) {
for (final String key : inputMap.keySet()) {
if (key != null) {
final boolean passwordType = key.toLowerCase().contains("password");
String value;
value = bypassInputValidation
? inputMap.get(key)
: Validator.sanitizeInputValue(configuration, inputMap.get(key), maxLength);
value = passwordType && passwordTrim ? value.trim() : value;
value = !passwordType && trim ? value.trim() : value;
final String sanitizedName = Validator.sanitizeInputValue(configuration, key, maxLength);
outputMap.put(sanitizedName, value);
}
}
}
return Collections.unmodifiableMap(outputMap);
}
public Map<String, Object> readBodyAsJsonMap(final boolean bypassInputValidation)
throws IOException, PwmUnrecoverableException
{
final String bodyString = readRequestBodyAsString();
final Map<String, Object> inputMap = JsonUtil.deserializeMap(bodyString);
final boolean trim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_TRIM));
final boolean passwordTrim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_PASSWORD_TRIM));
final int maxLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final Map<String, Object> outputMap = new LinkedHashMap<>();
if (inputMap != null) {
for (final String key : inputMap.keySet()) {
if (key != null) {
final boolean passwordType = key.toLowerCase().contains("password");
final Object value;
if (inputMap.get(key) instanceof String) {
String stringValue = bypassInputValidation
? (String)inputMap.get(key) :
Validator.sanitizeInputValue(configuration, (String)inputMap.get(key), maxLength);
stringValue = passwordType && passwordTrim ? stringValue.trim() : stringValue;
stringValue = !passwordType && trim ? stringValue.trim() : stringValue;
value = stringValue;
} else {
value = inputMap.get(key);
}
final String sanitizedName = Validator.sanitizeInputValue(configuration, key, maxLength);
outputMap.put(sanitizedName, value);
}
}
}
return Collections.unmodifiableMap(outputMap);
}
public PasswordData readParameterAsPassword(final String name)
throws PwmUnrecoverableException
{
final int maxLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final boolean trim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_PASSWORD_TRIM));
final String rawValue = httpServletRequest.getParameter(name);
if (rawValue != null && !rawValue.isEmpty()) {
final String decodedValue = decodeStringToDefaultCharSet(rawValue);
final String sanitizedValue = Validator.sanitizeInputValue(configuration, decodedValue, maxLength);
if (sanitizedValue != null) {
final String trimmedVale = trim ? sanitizedValue.trim() : sanitizedValue;
return new PasswordData(trimmedVale);
}
}
return null;
}
public String readParameterAsString(final String name, final int maxLength, final Flag... flags)
throws PwmUnrecoverableException {
final List<String> results = readParameterAsStrings(name, maxLength, flags);
if (results == null || results.isEmpty()) {
return "";
}
return results.iterator().next();
}
public String readParameterAsString(final String name, final String valueIfNotPresent)
throws PwmUnrecoverableException {
final int maxLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final String returnValue = readParameterAsString(name, maxLength);
return returnValue == null || returnValue.isEmpty() ? valueIfNotPresent : returnValue;
}
public boolean hasParameter(final String name)
throws PwmUnrecoverableException {
return this.getHttpServletRequest().getParameterMap().containsKey(name);
}
public String readParameterAsString(final String name, final Flag... flags)
throws PwmUnrecoverableException {
final int maxLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
return readParameterAsString(name, maxLength, flags);
}
public boolean readParameterAsBoolean(final String name)
throws PwmUnrecoverableException {
final String strValue = readParameterAsString(name);
return strValue != null && Boolean.parseBoolean(strValue);
}
public int readParameterAsInt(final String name, final int defaultValue)
throws PwmUnrecoverableException {
final String strValue = readParameterAsString(name);
try {
return Integer.parseInt(strValue);
} catch (NumberFormatException e) {
return defaultValue;
}
}
public List<String> readParameterAsStrings(
final String name,
final int maxLength,
final Flag... flags
)
throws PwmUnrecoverableException
{
final boolean bypassInputValidation = flags != null && Arrays.asList(flags).contains(Flag.BypassValidation);
final HttpServletRequest req = this.getHttpServletRequest();
final boolean trim = Boolean.parseBoolean(configuration.readAppProperty(AppProperty.SECURITY_INPUT_TRIM));
final String[] rawValues = req.getParameterValues(name);
if (rawValues == null || rawValues.length == 0) {
return Collections.emptyList();
}
final List<String> resultSet = new ArrayList<>();
for (final String rawValue : rawValues) {
final String decodedValue = decodeStringToDefaultCharSet(rawValue);
final String sanitizedValue = bypassInputValidation
? decodedValue
: Validator.sanitizeInputValue(configuration, decodedValue, maxLength);
if (sanitizedValue.length() > 0) {
resultSet.add(trim ? sanitizedValue.trim() : sanitizedValue);
}
}
return resultSet;
}
public String readHeaderValueAsString(final HttpHeader headerName) {
return readHeaderValueAsString(headerName.getHttpName());
}
public String readHeaderValueAsString(final String headerName) {
final int maxChars = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final HttpServletRequest req = this.getHttpServletRequest();
final String rawValue = req.getHeader(headerName);
final String sanitizedInputValue = Validator.sanitizeInputValue(configuration, rawValue, maxChars);
return Validator.sanitizeHeaderValue(configuration, sanitizedInputValue);
}
public Map<String, List<String>> readHeaderValuesMap() {
final int maxChars = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final HttpServletRequest req = this.getHttpServletRequest();
final Map<String, List<String>> returnObj = new LinkedHashMap<>();
for (final Enumeration<String> headerNameEnum = req.getHeaderNames(); headerNameEnum.hasMoreElements(); ) {
final String headerName = headerNameEnum.nextElement();
final List<String> valueList = new ArrayList<>();
for (final Enumeration<String> headerValueEnum = req.getHeaders(headerName); headerValueEnum.hasMoreElements(); ) {
final String headerValue = headerValueEnum.nextElement();
final String sanitizedInputValue = Validator.sanitizeInputValue(configuration, headerValue, maxChars);
final String sanitizedHeaderValue = Validator.sanitizeHeaderValue(configuration, sanitizedInputValue);
if (sanitizedHeaderValue != null && !sanitizedHeaderValue.isEmpty()) {
valueList.add(sanitizedHeaderValue);
}
}
if (!valueList.isEmpty()) {
returnObj.put(headerName, Collections.unmodifiableList(valueList));
}
}
return Collections.unmodifiableMap(returnObj);
}
public List<String> parameterNames() {
final int maxChars = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final List<String> returnObj = new ArrayList();
for (final Enumeration nameEnum = getHttpServletRequest().getParameterNames(); nameEnum.hasMoreElements(); ) {
final String paramName = nameEnum.nextElement().toString();
final String returnName = Validator.sanitizeInputValue(configuration, paramName, maxChars);
returnObj.add(returnName);
}
return Collections.unmodifiableList(returnObj);
}
public Map<String, String> readParametersAsMap()
throws PwmUnrecoverableException {
final Map<String, String> returnObj = new HashMap<>();
for (final String paramName : parameterNames()) {
final String paramValue = readParameterAsString(paramName);
returnObj.put(paramName, paramValue);
}
return Collections.unmodifiableMap(returnObj);
}
public Map<String, List<String>> readMultiParametersAsMap()
throws PwmUnrecoverableException {
final int maxLength = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_PARAM_MAX_READ_LENGTH));
final Map<String, List<String>> returnObj = new HashMap<>();
for (final String paramName : parameterNames()) {
final List<String> values = readParameterAsStrings(paramName, maxLength);
returnObj.put(paramName, values);
}
return Collections.unmodifiableMap(returnObj);
}
public String readCookie(final String cookieName) {
final int maxChars = Integer.parseInt(configuration.readAppProperty(AppProperty.HTTP_COOKIE_MAX_READ_LENGTH));
final Cookie[] cookies = this.getHttpServletRequest().getCookies();
if (cookies != null) {
for (final Cookie cookie : cookies) {
if (cookie.getName() != null && cookie.getName().equals(cookieName)) {
final String rawCookieValue = cookie.getValue();
final String decodedCookieValue = StringUtil.urlDecode(rawCookieValue);
return Validator.sanitizeInputValue(configuration, decodedCookieValue, maxChars);
}
}
}
return null;
}
private static String decodeStringToDefaultCharSet(final String input) {
String decodedValue = input;
try {
decodedValue = new String(input.getBytes("ISO-8859-1"), PwmConstants.DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
LOGGER.error("error decoding request parameter: " + e.getMessage());
}
return decodedValue;
}
public HttpMethod getMethod() {
return HttpMethod.fromString(this.getHttpServletRequest().getMethod());
}
}