blob: fb8d1b56639f95d65b8486c8bf10500aa08abd5c [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.ofbiz.base.util;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.FileNameMap;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Currency;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.ofbiz.entity.Delegator;
import org.apache.ofbiz.entity.util.EntityUtilProperties;
import org.apache.ofbiz.webapp.control.ConfigXMLReader;
import org.apache.ofbiz.webapp.control.SameSiteFilter;
import org.apache.ofbiz.webapp.event.FileUploadProgressListener;
import org.apache.ofbiz.widget.renderer.VisualTheme;
import com.google.re2j.Matcher;
import com.google.re2j.Pattern;
/**
* HttpUtil - Misc HTTP Utility Functions
*/
public final class UtilHttp {
private static final String MODULE = UtilHttp.class.getName();
private static final String MULTI_ROW_DELIMITER = "_o_";
private static final String ROW_SUBMIT_PREFIX = "_rowSubmit_o_";
private static final String COMPOSITE_DELIMITER = "_c_";
private static final int MULTI_ROW_DELIMITER_LENGTH = MULTI_ROW_DELIMITER.length();
private static final int ROW_SUBMIT_PREFIX_LENGTH = ROW_SUBMIT_PREFIX.length();
private static final String SESSION_KEY_TIMEZONE = "timeZone";
private static final String SESSION_KEY_THEME = "visualTheme";
private UtilHttp() { }
/**
* Create a combined map from servlet context, session, attributes and parameters
* @return The resulting Map
*/
public static Map<String, Object> getCombinedMap(HttpServletRequest request) {
return getCombinedMap(request, null);
}
/**
* Create a combined map from servlet context, session, attributes and parameters
* -- this method will only use the skip names for session and servlet context attributes
* @return The resulting Map
*/
public static Map<String, Object> getCombinedMap(HttpServletRequest request, Set<? extends String> namesToSkip) {
Map<String, Object> combinedMap = new HashMap<>();
combinedMap.putAll(getParameterMap(request)); // parameters override nothing
combinedMap.putAll(getServletContextMap(request, namesToSkip)); // bottom level application attributes
combinedMap.putAll(getSessionMap(request, namesToSkip)); // session overrides application
combinedMap.putAll(getAttributeMap(request)); // attributes trump them all
return combinedMap;
}
/**
* Creates a canonicalized parameter map from a HTTP request.
* <p>
* If parameters are empty, the multi-part parameter map will be used.
* @param request the HTTP request containing the parameters
* @return a canonicalized parameter map.
*/
public static Map<String, Object> getParameterMap(HttpServletRequest request) {
return getParameterMap(request, x -> true);
}
/**
* Creates a canonicalized parameter map from a HTTP request.
* <p>
* If parameters are empty, the multi-part parameter map will be used.
* @param req the HTTP request containing the parameters
* @param pred the predicate filtering the parameter names
* @return a canonicalized parameter map.
*/
public static Map<String, Object> getParameterMap(HttpServletRequest req, Predicate<String> pred) {
// Add all the actual HTTP request parameters
Map<String, String[]> origParams = req.getParameterMap();
Map<String, Object> params = origParams.entrySet().stream()
.filter(pair -> pred.test(pair.getKey()))
.collect(toMap(Map.Entry::getKey, pair -> transformParamValue(pair.getValue())));
// Pseudo-parameters passed in the URI path overrides the ones from the regular URI parameters
params.putAll(getPathInfoOnlyParameterMap(req.getPathInfo(), pred));
// If nothing is found in the parameters, try to find something in the multi-part map.
Map<String, Object> multiPartMap = params.isEmpty() ? getMultiPartParameterMap(req) : Collections.emptyMap();
params.putAll(multiPartMap);
if (req.getAttribute("multiPartMap") == null) {
req.setAttribute("multiPartMap", multiPartMap);
}
if (Debug.verboseOn()) {
Debug.logVerbose("Made Request Parameter Map with [" + params.size() + "] Entries", MODULE);
}
// Handles encoded queryStrings
String requestURI = req.getRequestURI();
if (params.isEmpty() && null != requestURI) {
try {
List<NameValuePair> nameValuePairs = URLEncodedUtils.parse(new URI(URLDecoder.decode(requestURI, "UTF-8")),
Charset.forName("UTF-8"));
for (NameValuePair element : nameValuePairs) {
params.put(element.getName(), element.getValue());
}
} catch (UnsupportedEncodingException | URISyntaxException e) {
Debug.logError("Can't handle encoded queryString " + requestURI, MODULE);
}
}
return canonicalizeParameterMap(params);
}
/**
* Transforms a string array into either a list of string or string.
* <p>
* This is meant to facilitate the work of request handlers.
* @param value the array of string to prepare
* @return the adapted value.
* @throws NullPointerException when {@code value} is {@code null}.
*/
private static Object transformParamValue(String[] value) {
return value.length == 1 ? value[0] : Arrays.asList(value);
}
public static Map<String, Object> getMultiPartParameterMap(HttpServletRequest request) {
Map<String, Object> multiPartMap = new HashMap<>();
Delegator delegator = (Delegator) request.getAttribute("delegator");
HttpSession session = request.getSession();
boolean isMultiPart = ServletFileUpload.isMultipartContent(request);
if (isMultiPart) {
long maxUploadSize = getMaxUploadSize(delegator);
int sizeThreshold = getSizeThreshold(delegator);
File tmpUploadRepository = getTmpUploadRepository(delegator);
String encoding = request.getCharacterEncoding();
// check for multipart content types which may have uploaded items
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory(sizeThreshold, tmpUploadRepository));
upload.setSizeMax(maxUploadSize);
// create the progress listener and add it to the session
FileUploadProgressListener listener = new FileUploadProgressListener();
upload.setProgressListener(listener);
session.setAttribute("uploadProgressListener", listener);
if (encoding != null) {
upload.setHeaderEncoding(encoding);
}
List<FileItem> uploadedItems = null;
try {
uploadedItems = UtilGenerics.cast(upload.parseRequest(request));
} catch (FileUploadException e) {
Debug.logError("File upload error" + e, MODULE);
}
if (uploadedItems != null) {
request.setAttribute("fileItems", uploadedItems);
for (FileItem item : uploadedItems) {
String fieldName = item.getFieldName();
//byte[] itemBytes = item.get();
/*
Debug.logInfo("Item Info [" + fieldName + "] : " + item.getName() + " / " + item.getSize() + " / " +
item.getContentType() + " FF: " + item.isFormField(), MODULE);
*/
if (item.isFormField() || item.getName() == null) {
if (multiPartMap.containsKey(fieldName)) {
Object mapValue = multiPartMap.get(fieldName);
if (mapValue instanceof List<?>) {
UtilGenerics.checkCollection(mapValue, Object.class).add(item.getString());
} else if (mapValue instanceof String) {
List<String> newList = new LinkedList<>();
newList.add((String) mapValue);
newList.add(item.getString());
multiPartMap.put(fieldName, newList);
} else {
Debug.logWarning("Form field found [" + fieldName + "] which was not handled!", MODULE);
}
} else {
if (encoding != null) {
try {
multiPartMap.put(fieldName, item.getString(encoding));
} catch (java.io.UnsupportedEncodingException uee) {
Debug.logError(uee, "Unsupported Encoding, using deafault", MODULE);
multiPartMap.put(fieldName, item.getString());
}
} else {
multiPartMap.put(fieldName, item.getString());
}
}
/* OFBIZ-10833 - Set the consumed parameters in request attributes for enctype="multipart/form-data" type form
* so that it will be available for the next response. Please refer Jira for more details.
*/
request.setAttribute(fieldName, multiPartMap.get(fieldName));
} else {
String fileName = item.getName();
if (fileName.indexOf('\\') > -1 || fileName.indexOf('/') > -1) {
// get just the file name IE and other browsers also pass in the local path
int lastIndex = fileName.lastIndexOf('\\');
if (lastIndex == -1) {
lastIndex = fileName.lastIndexOf('/');
}
if (lastIndex > -1) {
fileName = fileName.substring(lastIndex + 1);
}
}
multiPartMap.put(fieldName, ByteBuffer.wrap(item.get()));
multiPartMap.put("_" + fieldName + "_fileItem", item);
multiPartMap.put("_" + fieldName + "_size", item.getSize());
multiPartMap.put("_" + fieldName + "_fileName", fileName);
multiPartMap.put("_" + fieldName + "_contentType", item.getContentType());
}
}
}
}
return multiPartMap;
}
/**
* @param delegator
* @return maxUploadSize
*/
public static long getMaxUploadSize(Delegator delegator) {
// get the HTTP upload configuration
String maxSizeStr = EntityUtilProperties.getPropertyValue("general", "http.upload.max.size", "-1", delegator);
long maxUploadSize = -1;
try {
maxUploadSize = Long.parseLong(maxSizeStr);
} catch (NumberFormatException e) {
Debug.logError(e, "Unable to obtain the max upload size from general.properties; using default -1", MODULE);
maxUploadSize = -1;
}
return maxUploadSize;
}
/**
* @param delegator
* @return sizeThreshold
*/
public static int getSizeThreshold(Delegator delegator) {
// get the HTTP size threshold configuration - files bigger than this will be
// temporarily stored on disk during upload
String sizeThresholdStr = EntityUtilProperties.getPropertyValue("general", "http.upload.max.sizethreshold",
"10240", delegator);
int sizeThreshold = 10240; // 10K
try {
sizeThreshold = Integer.parseInt(sizeThresholdStr);
} catch (NumberFormatException e) {
Debug.logError(e, "Unable to obtain the threshold size from general.properties; using default 10K", MODULE);
sizeThreshold = -1;
}
return sizeThreshold;
}
/**
* @param delegator
* @return tmpUploadRepository
*/
public static File getTmpUploadRepository(Delegator delegator) {
// directory used to temporarily store files that are larger than the configured size threshold
String tmpUploadRepository = EntityUtilProperties.getPropertyValue("general", "http.upload.tmprepository",
"runtime/tmp", delegator);
return new File(tmpUploadRepository);
}
public static Map<String, Object> getQueryStringOnlyParameterMap(String queryString) {
Map<String, Object> paramMap = new HashMap<>();
if (UtilValidate.isNotEmpty(queryString)) {
StringTokenizer queryTokens = new StringTokenizer(queryString, "&");
while (queryTokens.hasMoreTokens()) {
String token = queryTokens.nextToken();
if (token.startsWith("amp;")) {
// this is most likely a split value that had an &amp; in it, so don't consider this a name; note that some old code just
// stripped the "amp;" and went with it
continue;
}
int equalsIndex = token.indexOf("=");
if (equalsIndex > 0) {
String name = token.substring(0, equalsIndex);
String paramValue = UtilCodec.getDecoder("url").decode(token.substring(equalsIndex + 1));
if (UtilValidate.isEmpty(paramMap.get(name))) {
paramMap.put(name, paramValue);
} else {
if (paramMap.get(name) instanceof Collection<?>) {
List<String> valueList = UtilGenerics.cast(paramMap.get(name));
valueList.add(paramValue);
paramMap.put(name, valueList);
} else {
paramMap.put(name, UtilMisc.toList(paramMap.get(name), paramValue));
}
}
}
}
}
return canonicalizeParameterMap(paramMap);
}
/**
* Extracts the parameters that are passed in the URI path.
* <p>
* path parameters are denoted by "/~KEY0=VALUE0/~KEY1=VALUE1/".
* This is an obsolete syntax for passing parameters to request handlers.
* @param path the URI path part which can be {@code null}
* @param pred the predicate filtering parameter names
* @return a canonicalized parameter map.
*/
static Map<String, Object> getPathInfoOnlyParameterMap(String path, Predicate<String> pred) {
String path1 = Optional.ofNullable(path).orElse("");
Map<String, List<String>> allParams = Arrays.stream(path1.split("/"))
.filter(segment -> segment.startsWith("~") && segment.contains("="))
.map(kv -> kv.substring(1).split("="))
.collect(groupingBy(kv -> kv[0], mapping(kv -> kv[1], toList())));
// Filter and canonicalize the parameter map.
Function<List<String>, Object> canonicalize = val -> (val.size() == 1) ? val.get(0) : val;
return allParams.entrySet().stream()
.filter(pair -> pred.test(pair.getKey()))
.collect(collectingAndThen(toMap(Map.Entry::getKey, canonicalize.compose(Map.Entry::getValue)),
UtilHttp::canonicalizeParameterMap));
}
public static Map<String, Object> getUrlOnlyParameterMap(HttpServletRequest request) {
// NOTE: these have already been through canonicalizeParameterMap, so not doing it again here
Map<String, Object> paramMap = getQueryStringOnlyParameterMap(request.getQueryString());
paramMap.putAll(getPathInfoOnlyParameterMap(request.getPathInfo(), x -> true));
return paramMap;
}
public static Map<String, Object> canonicalizeParameterMap(Map<String, Object> paramMap) {
for (Map.Entry<String, Object> paramEntry : paramMap.entrySet()) {
if (paramEntry.getValue() instanceof String) {
String paramEntries = (String) paramEntry.getValue();
String[] stringValues = paramEntries.split(" ");
String params = "";
// Handles textareas, see OFBIZ-12249
if (stringValues.length > 0 && !paramEntry.getKey().equals("DUMMYPAGE")) {
for (String s : stringValues) {
// if the string contains only an URL beginning by http or ftp => no change to keep special chars
if (UtilValidate.isValidUrl(s) && (s.indexOf("://") == 4 || s.indexOf("://") == 3)) {
params = params + s + " ";
} else if (UtilValidate.isUrl(s) && !s.isEmpty()) {
// if the string contains not only an URL => concatenate possible canonicalized before and after, w/o changing the URL
String url = extractUrls(s).get(0); // There should be only 1 URL in a block, makes no sense else
int start = s.indexOf(url);
String after = (String) s.subSequence(start + url.length(), s.length());
params = params + canonicalizeParameter((String) s.subSequence(0, start)) + url + canonicalizeParameter(after) + " ";
} else {
// Simple string to canonicalize
params = params + canonicalizeParameter(s) + " ";
}
}
paramEntry.setValue(params.trim());
} else {
paramEntry.setValue(canonicalizeParameter(paramEntries));
}
} else if (paramEntry.getValue() instanceof Collection<?>) {
List<String> newList = new LinkedList<>();
for (String listEntry : UtilGenerics.<Collection<String>>cast(paramEntry.getValue())) {
newList.add(canonicalizeParameter(listEntry));
}
paramEntry.setValue(newList);
}
}
return paramMap;
}
public static String canonicalizeParameter(String paramValue) {
try {
/** calling canonicalize with strict flag set to false so we only get warnings about double encoding, etc; can be set to true for
* exceptions and more security */
String cannedStr = UtilCodec.canonicalize(paramValue, false);
if (Debug.verboseOn()) {
Debug.logVerbose("Canonicalized parameter with " + (cannedStr.equals(paramValue) ? "no " : "") + "change: original ["
+ paramValue + "] canned [" + cannedStr + "]", MODULE);
}
return cannedStr;
} catch (Exception e) {
Debug.logError(e, "Error in canonicalize parameter value [" + paramValue + "]: " + e.toString(), MODULE);
return paramValue;
}
}
/**
* Create a map from a HttpRequest (attributes) object used in JSON requests
* @return The resulting Map
*/
public static Map<String, Object> getJSONAttributeMap(HttpServletRequest request) {
return parseJSONAttributeMap(getAttributeMap(request));
}
/**
* For a map analyse each object to prepare a Json response
* @param attrMap
* @return
*/
private static Map<String, Object> parseJSONAttributeMap(Map<String, Object> attrMap) {
Map<String, Object> returnMap = new HashMap<>();
for (Map.Entry<String, Object> entry : attrMap.entrySet()) {
Object val = parseJSONAttributeValue(entry.getValue());
if (val != null) {
returnMap.put(entry.getKey(), val);
}
}
return returnMap;
}
/**
* For a value analyse the object type to prepare a Json response
* @param val
* @return
*/
private static Object parseJSONAttributeValue(Object val) {
if (val == null) {
return null;
}
if (val instanceof java.sql.Timestamp) {
val = val.toString();
}
if (val instanceof String
|| val instanceof Number
|| val instanceof Boolean) {
return val;
} else if (val instanceof Map<?, ?>) {
return parseJSONAttributeMap(UtilGenerics.cast(val));
} else if (val instanceof List<?>) {
return ((List<?>) val).stream().map(UtilHttp::parseJSONAttributeValue).collect(Collectors.toList());
}
return null;
}
/**
* Create a map from a HttpRequest (attributes) object
* @return The resulting Map
*/
public static Map<String, Object> getAttributeMap(HttpServletRequest request) {
return getAttributeMap(request, null);
}
/**
* Create a map from a HttpRequest (attributes) object
* @return The resulting Map
*/
public static Map<String, Object> getAttributeMap(HttpServletRequest request, Set<? extends String> namesToSkip) {
Map<String, Object> attributeMap = new HashMap<>();
// look at all request attributes
Enumeration<String> requestAttrNames = UtilGenerics.cast(request.getAttributeNames());
while (requestAttrNames.hasMoreElements()) {
String attrName = requestAttrNames.nextElement();
if (namesToSkip != null && namesToSkip.contains(attrName)) {
continue;
}
Object attrValue = request.getAttribute(attrName);
attributeMap.put(attrName, attrValue);
}
if (Debug.verboseOn()) {
Debug.logVerbose("Made Request Attribute Map with [" + attributeMap.size() + "] Entries", MODULE);
Debug.logVerbose("Request Attribute Map Entries: " + System.getProperty("line.separator") + UtilMisc.printMap(attributeMap), MODULE);
}
return attributeMap;
}
/**
* Create a map from a HttpSession object
* @return The resulting Map
*/
public static Map<String, Object> getSessionMap(HttpServletRequest request) {
return getSessionMap(request, null);
}
/**
* Create a map from a HttpSession object
* @return The resulting Map
*/
public static Map<String, Object> getSessionMap(HttpServletRequest request, Set<? extends String> namesToSkip) {
Map<String, Object> sessionMap = new HashMap<>();
HttpSession session = request.getSession();
// look at all the session attributes
Enumeration<String> sessionAttrNames = UtilGenerics.cast(session.getAttributeNames());
while (sessionAttrNames.hasMoreElements()) {
String attrName = sessionAttrNames.nextElement();
if (namesToSkip != null && namesToSkip.contains(attrName)) {
continue;
}
Object attrValue = session.getAttribute(attrName);
sessionMap.put(attrName, attrValue);
}
if (Debug.verboseOn()) {
Debug.logVerbose("Made Session Attribute Map with [" + sessionMap.size() + "] Entries", MODULE);
Debug.logVerbose("Session Attribute Map Entries: " + System.getProperty("line.separator") + UtilMisc.printMap(sessionMap), MODULE);
}
return sessionMap;
}
/**
* Create a map from a ServletContext object
* @return The resulting Map
*/
public static Map<String, Object> getServletContextMap(HttpServletRequest request) {
return getServletContextMap(request, null);
}
/**
* Create a map from a ServletContext object
* @return The resulting Map
*/
public static Map<String, Object> getServletContextMap(HttpServletRequest request, Set<? extends String> namesToSkip) {
Map<String, Object> servletCtxMap = new HashMap<>();
// look at all servlet context attributes
Enumeration<String> applicationAttrNames = request.getServletContext().getAttributeNames();
while (applicationAttrNames.hasMoreElements()) {
String attrName = applicationAttrNames.nextElement();
if (namesToSkip != null && namesToSkip.contains(attrName)) {
continue;
}
Object attrValue = request.getServletContext().getAttribute(attrName);
servletCtxMap.put(attrName, attrValue);
}
if (Debug.verboseOn()) {
Debug.logVerbose("Made ServletContext Attribute Map with [" + servletCtxMap.size() + "] Entries", MODULE);
Debug.logVerbose("ServletContext Attribute Map Entries: " + System.getProperty("line.separator") + UtilMisc.printMap(servletCtxMap),
MODULE);
}
return servletCtxMap;
}
public static Map<String, Object> makeParamMapWithPrefix(HttpServletRequest request, String prefix, String suffix) {
return makeParamMapWithPrefix(request, null, prefix, suffix);
}
public static Map<String, Object> makeParamMapWithPrefix(HttpServletRequest request, Map<String, ? extends Object> additionalFields,
String prefix, String suffix) {
return makeParamMapWithPrefix(getCombinedMap(request), additionalFields, prefix, suffix);
}
public static Map<String, Object> makeParamMapWithPrefix(Map<String, ? extends Object> context, Map<String, ? extends Object> additionalFields,
String prefix, String suffix) {
Map<String, Object> paramMap = new HashMap<>();
for (Map.Entry<String, ? extends Object> entry : context.entrySet()) {
String parameterName = entry.getKey();
if (parameterName.startsWith(prefix)) {
if (UtilValidate.isNotEmpty(suffix)) {
if (parameterName.endsWith(suffix)) {
String key = parameterName.substring(prefix.length(), parameterName.length() - (suffix.length()));
if (entry.getValue() instanceof ByteBuffer) {
ByteBuffer value = (ByteBuffer) entry.getValue();
paramMap.put(key, value);
} else {
String value = (String) entry.getValue();
paramMap.put(key, value);
}
}
} else {
String key = parameterName.substring(prefix.length());
if (context.get(parameterName) instanceof ByteBuffer) {
ByteBuffer value = (ByteBuffer) entry.getValue();
paramMap.put(key, value);
} else {
String value = (String) entry.getValue();
paramMap.put(key, value);
}
}
}
}
if (additionalFields != null) {
for (Map.Entry<String, ? extends Object> entry : additionalFields.entrySet()) {
String fieldName = entry.getKey();
if (fieldName.startsWith(prefix)) {
if (UtilValidate.isNotEmpty(suffix)) {
if (fieldName.endsWith(suffix)) {
String key = fieldName.substring(prefix.length(), fieldName.length() - (suffix.length() - 1));
Object value = entry.getValue();
paramMap.put(key, value);
// check for image upload data
if (!(value instanceof String)) {
String nameKey = "_" + key + "_fileName";
Object nameVal = additionalFields.get("_" + fieldName + "_fileName");
if (nameVal != null) {
paramMap.put(nameKey, nameVal);
}
String typeKey = "_" + key + "_contentType";
Object typeVal = additionalFields.get("_" + fieldName + "_contentType");
if (typeVal != null) {
paramMap.put(typeKey, typeVal);
}
String sizeKey = "_" + key + "_size";
Object sizeVal = additionalFields.get("_" + fieldName + "_size");
if (sizeVal != null) {
paramMap.put(sizeKey, sizeVal);
}
}
}
} else {
String key = fieldName.substring(prefix.length());
Object value = entry.getValue();
paramMap.put(key, value);
// check for image upload data
if (!(value instanceof String)) {
String nameKey = "_" + key + "_fileName";
Object nameVal = additionalFields.get("_" + fieldName + "_fileName");
if (nameVal != null) {
paramMap.put(nameKey, nameVal);
}
String typeKey = "_" + key + "_contentType";
Object typeVal = additionalFields.get("_" + fieldName + "_contentType");
if (typeVal != null) {
paramMap.put(typeKey, typeVal);
}
String sizeKey = "_" + key + "_size";
Object sizeVal = additionalFields.get("_" + fieldName + "_size");
if (sizeVal != null) {
paramMap.put(sizeKey, sizeVal);
}
}
}
}
}
}
return paramMap;
}
/**
* Constructs a list of parameter values whose keys are matching a given prefix and suffix.
* @param request the HTTP request containing the parameters
* @param suffix the suffix that must be matched which can be {@code null}
* @param prefix the prefix that must be matched which can be {@code null}
* @return the list of parameter values whose keys are matching {@code prefix} and {@code suffix}.
* @throws NullPointerException when {@code request} is {@code null}.
*/
public static List<Object> makeParamListWithSuffix(HttpServletRequest request, String suffix, String prefix) {
return makeParamListWithSuffix(request, Collections.emptyMap(), suffix, prefix);
}
/**
* Constructs a list of parameter values whose keys are matching a given prefix and suffix.
* @param request the HTTP request containing the parameters
* @param additionalFields the additional parameters
* @param suffix the suffix that must be matched which can be {@code null}
* @param prefix the prefix that must be matched which can be {@code null}
* @return the list of parameter values whose keys are matching {@code prefix} and {@code suffix}.
* @throws NullPointerException when {@code request} or {@code additionalFields} are {@code null}.
*/
public static List<Object> makeParamListWithSuffix(HttpServletRequest request, Map<String, ?> additionalFields,
String suffix, String prefix) {
Objects.requireNonNull(request);
Objects.requireNonNull(additionalFields);
Predicate<Map.Entry<String, ?>> pred = UtilValidate.isEmpty(prefix)
? e -> e.getKey().endsWith(suffix)
: e -> e.getKey().endsWith(suffix) && e.getKey().startsWith(prefix);
Stream<Object> params = request.getParameterMap().entrySet().stream()
.filter(pred)
.map(e -> e.getValue()[0]);
Stream<Object> additionalParams = additionalFields.entrySet().stream()
.filter(pred)
.map(Map.Entry::getValue);
return Stream.concat(params, additionalParams).collect(Collectors.toList());
}
/**
* Given a request, returns the application name or "root" if deployed on root
* @param request An HttpServletRequest to get the name info from
* @return String
*/
public static String getApplicationName(HttpServletRequest request) {
String appName = "root";
if (request.getContextPath().length() > 1) {
appName = request.getContextPath().substring(1);
}
// When you set a mountpoint which contains a slash inside its name (ie not only a slash as a trailer, which is possible),
// as it's needed with OFBIZ-10765, OFBiz tries to create a cookie with a slash in its name and that's impossible.
return appName.replace("/", "_");
}
public static void setInitialRequestInfo(HttpServletRequest request) {
HttpSession session = request.getSession();
if (UtilValidate.isNotEmpty(session.getAttribute("_WEBAPP_NAME_"))) {
// oops, info already in place...
return;
}
String fullRequestUrl = getFullRequestUrl(request);
session.setAttribute("_WEBAPP_NAME_", getApplicationName(request));
session.setAttribute("_CLIENT_LOCALE_", request.getLocale());
session.setAttribute("_CLIENT_REQUEST_", fullRequestUrl);
session.setAttribute("_CLIENT_USER_AGENT_", request.getHeader("User-Agent") != null ? request.getHeader("User-Agent") : "");
session.setAttribute("_CLIENT_REFERER_", request.getHeader("Referer") != null ? request.getHeader("Referer") : "");
session.setAttribute("_CLIENT_FORWARDED_FOR_", request.getHeader("X-Forwarded-For"));
session.setAttribute("_CLIENT_REMOTE_ADDR_", request.getRemoteAddr());
session.setAttribute("_CLIENT_REMOTE_HOST_", request.getRemoteHost());
session.setAttribute("_CLIENT_REMOTE_USER_", request.getRemoteUser());
}
private static StringBuilder prepareServerRootUrl(HttpServletRequest request) {
StringBuilder requestUrl = new StringBuilder();
requestUrl.append(request.getScheme());
requestUrl.append("://" + request.getServerName());
if (request.getServerPort() != 80 && request.getServerPort() != 443) {
requestUrl.append(":" + request.getServerPort());
}
return requestUrl;
}
public static String getServerRootUrl(HttpServletRequest request) {
return prepareServerRootUrl(request).toString();
}
public static String getFullRequestUrl(HttpServletRequest request) {
StringBuilder requestUrl = prepareServerRootUrl(request);
requestUrl.append(request.getRequestURI());
if (request.getQueryString() != null) {
requestUrl.append("?" + request.getQueryString());
}
return requestUrl.toString();
}
/**
* Resolve the method send with the request.
* check first the parameter _method before return the request method
* @param request
* @return method
*/
public static String getRequestMethod(HttpServletRequest request) {
return request.getParameter("_method") != null
? request.getParameter("_method") : request.getMethod();
}
public static Locale getLocale(HttpServletRequest request, HttpSession session, Object appDefaultLocale) {
// check session first, should override all if anything set there
Object localeObject = session != null ? session.getAttribute("locale") : null;
// next see if the userLogin has a value
if (localeObject == null) {
Map<?, ?> userLogin = (Map<?, ?>) session.getAttribute("userLogin");
if (userLogin == null) {
userLogin = (Map<?, ?>) session.getAttribute("autoUserLogin");
}
if (userLogin != null) {
localeObject = userLogin.get("lastLocale");
}
}
// no user locale? before global default try appDefaultLocale if specified
if (localeObject == null && UtilValidate.isNotEmpty(appDefaultLocale)) {
localeObject = appDefaultLocale;
}
// finally request (w/ a fall back to default)
if (localeObject == null) {
localeObject = request != null ? request.getLocale() : null;
}
return UtilMisc.ensureLocale(localeObject);
}
/**
* Get the Locale object from a session variable; if not found use the browser's default
* @param request HttpServletRequest object to use for lookup
* @return Locale The current Locale to use
*/
public static Locale getLocale(HttpServletRequest request) {
if (request == null) {
return Locale.getDefault();
}
return getLocale(request, request.getSession(), null);
}
/**
* Get the Locale object from a session variable; if not found use the system's default.
* NOTE: This method is not recommended because it ignores the Locale from the browser not having the request object.
* @param session HttpSession object to use for lookup
* @return Locale The current Locale to use
*/
public static Locale getLocale(HttpSession session) {
if (session == null) {
return Locale.getDefault();
}
return getLocale(null, session, null);
}
public static void setLocale(HttpServletRequest request, String localeString) {
setLocale(request.getSession(), UtilMisc.parseLocale(localeString));
}
public static void setLocale(HttpSession session, Locale locale) {
session.setAttribute("locale", locale);
}
public static void setLocaleIfNone(HttpSession session, String localeString) {
if (UtilValidate.isNotEmpty(localeString) && session.getAttribute("locale") == null) {
setLocale(session, UtilMisc.parseLocale(localeString));
}
}
public static void setTimeZone(HttpServletRequest request, String tzId) {
setTimeZone(request.getSession(), UtilDateTime.toTimeZone(tzId));
}
public static void setTimeZone(HttpSession session, TimeZone timeZone) {
session.setAttribute(SESSION_KEY_TIMEZONE, timeZone);
}
public static void setTimeZoneIfNone(HttpSession session, String timeZoneString) {
if (UtilValidate.isNotEmpty(timeZoneString) && session.getAttribute(SESSION_KEY_TIMEZONE) == null) {
UtilHttp.setTimeZone(session, UtilDateTime.toTimeZone(timeZoneString));
}
}
public static TimeZone getTimeZone(HttpServletRequest request) {
HttpSession session = request.getSession();
TimeZone timeZone = (TimeZone) session.getAttribute(SESSION_KEY_TIMEZONE);
Map<String, String> userLogin = UtilGenerics.cast(session.getAttribute("userLogin"));
if (userLogin != null) {
String tzId = userLogin.get("lastTimeZone");
if (tzId != null) {
timeZone = TimeZone.getTimeZone(tzId);
}
}
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
session.setAttribute(SESSION_KEY_TIMEZONE, timeZone);
return timeZone;
}
public static TimeZone getTimeZone(HttpServletRequest request, HttpSession session, String appDefaultTimeZoneString) {
// check session first, should override all if anything set there
TimeZone timeZone = session != null ? (TimeZone) session.getAttribute(SESSION_KEY_TIMEZONE) : null;
// next see if the userLogin has a value
if (timeZone == null) {
Map<String, Object> userLogin = UtilGenerics.checkMap(session.getAttribute("userLogin"), String.class, Object.class);
if (userLogin == null) {
userLogin = UtilGenerics.checkMap(session.getAttribute("autoUserLogin"), String.class, Object.class);
}
if ((userLogin != null) && (UtilValidate.isNotEmpty(userLogin.get("lastTimeZone")))) {
timeZone = UtilDateTime.toTimeZone((String) userLogin.get("lastTimeZone"));
}
}
// if there is no user TimeZone, we will got the application default time zone (if provided)
if ((timeZone == null) && (UtilValidate.isNotEmpty(appDefaultTimeZoneString))) {
timeZone = UtilDateTime.toTimeZone(appDefaultTimeZoneString);
}
// finally request (w/ a fall back to default)
if (timeZone == null) {
timeZone = TimeZone.getDefault();
}
return timeZone;
}
/**
* Return the VisualTheme object from the user session
* @param request
* @return VisualTheme
*/
public static VisualTheme getVisualTheme(HttpServletRequest request) {
return (VisualTheme) request.getSession().getAttribute(SESSION_KEY_THEME);
}
public static void setVisualTheme(HttpServletRequest request, VisualTheme visualTheme) {
setVisualTheme(request.getSession(), visualTheme);
}
public static void setVisualTheme(HttpSession session, VisualTheme visualTheme) {
session.setAttribute(SESSION_KEY_THEME, visualTheme);
}
/**
* Get the currency string from the session.
* @param session HttpSession object to use for lookup
* @return String The ISO currency code
*/
public static String getCurrencyUom(HttpSession session, String appDefaultCurrencyUom) {
// session, should override all if set there
String iso = (String) session.getAttribute("currencyUom");
// check userLogin next, ie if nothing to override in the session
if (iso == null) {
Map<String, ?> userLogin = UtilGenerics.cast(session.getAttribute("userLogin"));
if (userLogin == null) {
userLogin = UtilGenerics.cast(session.getAttribute("autoUserLogin"));
}
if (userLogin != null) {
iso = (String) userLogin.get("lastCurrencyUom");
}
}
// no user currency? before global default try appDefaultCurrencyUom if specified
if (iso == null && UtilValidate.isNotEmpty(appDefaultCurrencyUom)) {
iso = appDefaultCurrencyUom;
}
// if none is set we will use the configured default
if (iso == null) {
try {
iso = UtilProperties.getPropertyValue("general", "currency.uom.id.default", "USD");
} catch (Exception e) {
Debug.logWarning("Error getting the general:currency.uom.id.default value: " + e.toString(), MODULE);
}
}
// if still none we will use the default for whatever currency we can get...
if (iso == null) {
Currency cur = Currency.getInstance(getLocale(session));
iso = cur.getCurrencyCode();
}
return iso;
}
/**
* Get the currency string from the session.
* @param request HttpServletRequest object to use for lookup
* @return String The ISO currency code
*/
public static String getCurrencyUom(HttpServletRequest request) {
return getCurrencyUom(request.getSession(), null);
}
/**
* Simple event to set the users per-session currency uom value
*/
public static void setCurrencyUom(HttpSession session, String currencyUom) {
session.setAttribute("currencyUom", currencyUom);
}
public static void setCurrencyUomIfNone(HttpSession session, String currencyUom) {
if (UtilValidate.isNotEmpty(currencyUom) && session.getAttribute("currencyUom") == null) {
session.setAttribute("currencyUom", currencyUom);
}
}
/**
* URL Encodes a Map of arguments
*/
public static String urlEncodeArgs(Map<String, ? extends Object> args) {
return urlEncodeArgs(args, true);
}
/**
* URL Encodes a Map of arguments
*/
public static String urlEncodeArgs(Map<String, ? extends Object> args, boolean useExpandedEntites) {
return urlEncodeArgs(args, useExpandedEntites, false);
}
/**
* URL Encodes a Map of arguments
*/
public static String urlEncodeArgs(Map<String, ? extends Object> args, boolean useExpandedEntites, boolean preserveEmpty) {
StringBuilder buf = new StringBuilder();
if (args != null) {
for (Map.Entry<String, ? extends Object> entry : args.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (preserveEmpty && value == null) {
value = "";
}
if (name == null || value == null) {
continue;
}
Collection<?> col;
if (value instanceof String) {
col = Arrays.asList(value);
} else if (value instanceof Collection) {
col = UtilGenerics.cast(value);
} else if (value.getClass().isArray()) {
col = Arrays.asList((Object[]) value);
} else {
col = Arrays.asList(value);
}
String valueStr = null;
for (Object colValue : col) {
if (colValue instanceof String) {
valueStr = (String) colValue;
} else if (colValue == null) {
continue;
} else {
valueStr = colValue.toString();
}
if (UtilValidate.isNotEmpty(valueStr) || preserveEmpty) {
if (buf.length() > 0) {
if (useExpandedEntites) {
buf.append("&amp;");
} else {
buf.append("&");
}
}
buf.append(UtilCodec.getEncoder("url").encode(name));
buf.append('=');
buf.append(UtilCodec.getEncoder("url").encode(valueStr));
}
}
}
}
return buf.toString();
}
/**
* Encodes a query parameter
* @throws UnsupportedEncodingException
*/
public static String getEncodedParameter(String parameter) throws UnsupportedEncodingException {
return URLEncoder.encode(parameter, "UTF-8");
}
public static String getRequestUriFromTarget(String target) {
if (UtilValidate.isEmpty(target)) {
return null;
}
int endOfRequestUri = target.length();
if (target.indexOf('?') > 0) {
endOfRequestUri = target.indexOf('?');
}
int slashBeforeRequestUri = target.lastIndexOf('/', endOfRequestUri);
String requestUri = null;
if (slashBeforeRequestUri < 0) {
requestUri = target.substring(0, endOfRequestUri);
} else {
requestUri = target.substring(slashBeforeRequestUri, endOfRequestUri);
}
return requestUri;
}
/**
* Returns the query string contained in a request target - basically everything
* after and including the ? character.
* @param target The request target
* @return The query string
*/
public static String getQueryStringFromTarget(String target) {
if (UtilValidate.isEmpty(target)) {
return "";
}
int queryStart = target.indexOf('?');
if (queryStart != -1) {
return target.substring(queryStart);
}
return "";
}
/**
* Removes the query string from a request target - basically everything
* after and including the ? character.
* @param target The request target
* @return The request target string
*/
public static String removeQueryStringFromTarget(String target) {
if (UtilValidate.isEmpty(target)) {
return null;
}
int queryStart = target.indexOf('?');
if (queryStart < 0) {
return target;
}
return target.substring(0, queryStart);
}
public static String getWebappMountPointFromTarget(String target) {
int firstChar = 0;
if (UtilValidate.isEmpty(target)) {
return null;
}
if (target.charAt(0) == '/') {
firstChar = 1;
}
int pathSep = target.indexOf('/', 1);
String webappMountPoint = null;
if (pathSep > 0) {
// if not then no good, supposed to be a inter-app, but there is no path sep! will do general search with null and treat like an intra-app
webappMountPoint = target.substring(firstChar, pathSep);
}
return webappMountPoint;
}
public static String encodeAmpersands(String htmlString) {
StringBuilder htmlBuffer = new StringBuilder(htmlString);
int ampLoc = -1;
while ((ampLoc = htmlBuffer.indexOf("&", ampLoc + 1)) != -1) {
//NOTE: this should work fine, but if it doesn't could try making sure all characters between & and ; are letters, that would qualify
// as an entity
// found ampersand, is it already and entity? if not change it to &amp;
int semiLoc = htmlBuffer.indexOf(";", ampLoc);
if (semiLoc != -1) {
// found a semi colon, if it has another & or an = before it, don't count it as an entity, otherwise it may be an entity, so skip it
int eqLoc = htmlBuffer.indexOf("=", ampLoc);
int amp2Loc = htmlBuffer.indexOf("&", ampLoc + 1);
if ((eqLoc == -1 || eqLoc > semiLoc) && (amp2Loc == -1 || amp2Loc > semiLoc)) {
continue;
}
}
// at this point not an entity, no substitute with a &amp;
htmlBuffer.insert(ampLoc + 1, "amp;");
}
return htmlBuffer.toString();
}
public static String encodeBlanks(String htmlString) {
return htmlString.replace(" ", "%20");
}
public static String setResponseBrowserProxyNoCache(HttpServletRequest request, HttpServletResponse response) {
setResponseBrowserProxyNoCache(response);
return "success";
}
public static void setResponseBrowserProxyNoCache(HttpServletResponse response) {
long nowMillis = System.currentTimeMillis();
response.setDateHeader("Expires", nowMillis);
response.setDateHeader("Last-Modified", nowMillis); // always modified
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, private"); // HTTP/1.1
response.setHeader("Pragma", "no-cache"); // HTTP/1.0
}
public static void setResponseBrowserDefaultSecurityHeaders(HttpServletResponse resp, ConfigXMLReader.ViewMap viewMap) {
// See https://cwiki.apache.org/confluence/display/OFBIZ/How+to+Secure+HTTP+Headers for details and how to test
String xFrameOption = null;
// HTTP Strict-Transport-Security (HSTS) enforces secure (HTTP over SSL/TLS) connections to the server.
String strictTransportSecurity = null;
if (viewMap != null) {
xFrameOption = viewMap.getxFrameOption();
strictTransportSecurity = viewMap.getStrictTransportSecurity();
}
// Default to sameorigin
if (UtilValidate.isNotEmpty(xFrameOption)) {
if (!"none".equals(xFrameOption)) {
resp.addHeader("x-frame-options", xFrameOption);
}
} else {
resp.addHeader("x-frame-options", "sameorigin");
}
// Default to "max-age=31536000; includeSubDomains" 31536000 secs = 1 year
if (UtilValidate.isNotEmpty(strictTransportSecurity)) {
if (!"none".equals(strictTransportSecurity)) {
resp.addHeader("strict-transport-security", strictTransportSecurity);
}
} else {
if (EntityUtilProperties.getPropertyAsBoolean("requestHandler", "strict-transport-security", true)) { // FIXME later pass req
// .getAttribute("delegator") as last argument
resp.addHeader("strict-transport-security", "max-age=31536000; includeSubDomains");
}
}
/** The only x-content-type-options defined value, "nosniff", prevents Internet Explorer from MIME-sniffing a response away from the
* declared content-type.
This also applies to Google Chrome, when downloading extensions. */
resp.addHeader("x-content-type-options", "nosniff");
/** This header enables the Cross-site scripting (XSS) filter built into most recent web browsers.
It's usually enabled by default anyway, so the role of this header is to re-enable the filter for this particular website if it was
disabled by the user.
This header is supported in IE 8+, and in Chrome (not sure which versions). The anti-XSS filter was added in Chrome 4. Its unknown if that
version honored this header.
FireFox has still an open bug entry and "offers" only the noscript plugin
https://wiki.mozilla.org/Security/Features/XSS_Filter
https://bugzilla.mozilla.org/show_bug.cgi?id=528661
**/
resp.addHeader("X-XSS-Protection", "1; mode=block");
resp.setHeader("Referrer-Policy", "no-referrer-when-downgrade"); // This is the default (in Firefox at least)
resp.setHeader("Content-Security-Policy-Report-Only", "default-src 'self'");
SameSiteFilter.addSameSiteCookieAttribute(resp);
// TODO in custom project. Public-Key-Pins-Report-Only is interesting but can't be used OOTB because of demos (the letsencrypt certificate
// is renewed every 3 months)
}
public static String getContentTypeByFileName(String fileName) {
FileNameMap mime = URLConnection.getFileNameMap();
return mime.getContentTypeFor(fileName);
}
/**
* Stream an array of bytes to the browser
* This method will close the ServletOutputStream when finished
* @param response HttpServletResponse object to get OutputStream from
* @param bytes Byte array of content to stream
* @param contentType The content type to pass to the browser
* @param fileName the fileName to tell the browser we are downloading
* @throws IOException
*/
public static void streamContentToBrowser(HttpServletResponse response, byte[] bytes, String contentType, String fileName) throws IOException {
// tell the browser not the cache
setResponseBrowserProxyNoCache(response);
// set the response info
response.setContentLength(bytes.length);
if (contentType != null) {
response.setContentType(contentType);
}
if (fileName != null) {
setContentDisposition(response, fileName);
}
// create the streams
// stream the content
try (OutputStream out = response.getOutputStream();
InputStream in = new ByteArrayInputStream(bytes)) {
streamContent(out, in, bytes.length);
out.flush();
} catch (IOException e) {
throw e;
}
}
public static void streamContentToBrowser(HttpServletResponse response, byte[] bytes, String contentType) throws IOException {
streamContentToBrowser(response, bytes, contentType, null);
}
/**
* Streams content from InputStream to the ServletOutputStream
* This method will close the ServletOutputStream when finished
* This method does not close the InputSteam passed
* @param response HttpServletResponse object to get OutputStream from
* @param in InputStream of the actual content
* @param length Size (in bytes) of the content
* @param contentType The content type to pass to the browser
* @throws IOException
*/
public static void streamContentToBrowser(HttpServletResponse response, InputStream in, int length, String contentType, String fileName)
throws IOException {
// tell the browser not the cache
setResponseBrowserProxyNoCache(response);
// set the response info
response.setContentLength(length);
if (contentType != null) {
response.setContentType(contentType);
}
if (fileName != null) {
setContentDisposition(response, fileName);
}
// stream the content
try (OutputStream out = response.getOutputStream()) {
streamContent(out, in, length);
out.flush();
} catch (IOException e) {
throw e;
}
}
public static void streamContentToBrowser(HttpServletResponse response, InputStream in, int length, String contentType) throws IOException {
streamContentToBrowser(response, in, length, contentType, null);
}
/**
* Stream binary content from InputStream to OutputStream
* This method does not close the streams passed
* @param out OutputStream content should go to
* @param in InputStream of the actual content
* @param length Size (in bytes) of the content
* @throws IOException
*/
private static void streamContent(OutputStream out, InputStream in, int length) throws IOException {
// make sure we have something to write to
if (out == null) {
throw new IOException("Attempt to write to null output stream");
}
// make sure we have something to read from
if (in == null) {
throw new IOException("Attempt to read from null input stream");
}
// make sure we have some content
if (length == 0) {
throw new IOException("Attempt to write 0 bytes of content to output stream");
}
// initialize the buffered streams
int bufferSize = EntityUtilProperties.getPropertyAsInteger("content", "stream.buffersize", 8192);
byte[] buffer = new byte[bufferSize];
int read = 0;
try (BufferedOutputStream bos = new BufferedOutputStream(out, bufferSize);
BufferedInputStream bis = new BufferedInputStream(in, bufferSize)) {
while ((read = bis.read(buffer, 0, buffer.length)) != -1) {
bos.write(buffer, 0, read);
}
} catch (IOException e) {
Debug.logError(e, "Problem reading/writing buffers", MODULE);
throw e;
}
}
public static String stripViewParamsFromQueryString(String queryString) {
return stripViewParamsFromQueryString(queryString, null);
}
public static String stripViewParamsFromQueryString(String queryString, String paginatorNumber) {
Set<String> paramNames = new HashSet<>();
if (UtilValidate.isNotEmpty(paginatorNumber)) {
paginatorNumber = "_" + paginatorNumber;
}
paramNames.add("VIEW_INDEX" + paginatorNumber);
paramNames.add("VIEW_SIZE" + paginatorNumber);
paramNames.add("viewIndex" + paginatorNumber);
paramNames.add("viewSize" + paginatorNumber);
return stripNamedParamsFromQueryString(queryString, paramNames);
}
public static String stripNamedParamsFromQueryString(String queryString, Collection<String> paramNames) {
String retStr = null;
if (UtilValidate.isNotEmpty(queryString)) {
StringTokenizer queryTokens = new StringTokenizer(queryString, "&");
StringBuilder cleanQuery = new StringBuilder();
while (queryTokens.hasMoreTokens()) {
String token = queryTokens.nextToken();
if (token.startsWith("amp;")) {
token = token.substring(4);
}
int equalsIndex = token.indexOf("=");
String name = token;
if (equalsIndex > 0) {
name = token.substring(0, equalsIndex);
}
if (!paramNames.contains(name)) {
if (cleanQuery.length() > 0) {
cleanQuery.append("&");
}
cleanQuery.append(token);
}
}
retStr = cleanQuery.toString();
}
return retStr;
}
/**
* Given multi form data with the ${param}_o_N notation, creates a Collection
* of Maps for the submitted rows. Each Map contains the key/value pairs
* of a particular row. The keys will be stripped of the _o_N suffix.
* There is an additionaly key "row" for each Map that holds the
* index of the row.
*/
public static Collection<Map<String, Object>> parseMultiFormData(Map<String, Object> parameters) {
Map<Integer, Map<String, Object>> rows = new HashMap<>(); // stores the rows keyed by row number
// first loop through all the keys and create a hashmap for each ${ROW_SUBMIT_PREFIX}${N} = Y
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
String key = entry.getKey();
// skip everything that is not ${ROW_SUBMIT_PREFIX}N
if (key == null || key.length() <= ROW_SUBMIT_PREFIX_LENGTH) {
continue;
}
if (key.indexOf(MULTI_ROW_DELIMITER) <= 0) {
continue;
}
if (!key.substring(0, ROW_SUBMIT_PREFIX_LENGTH).equals(ROW_SUBMIT_PREFIX)) {
continue;
}
if (!"Y".equals(entry.getValue())) {
continue;
}
// decode the value of N and create a new map for it
Integer n = Integer.decode(key.substring(ROW_SUBMIT_PREFIX_LENGTH, key.length()));
Map<String, Object> m = new HashMap<>();
m.put("row", n); // special "row" = N tuple
rows.put(n, m); // key it to N
}
// next put all parameters with matching N in the right map
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
String key = entry.getKey();
// skip keys without DELIMITER and skip ROW_SUBMIT_PREFIX
if (key == null) {
continue;
}
int index = key.indexOf(MULTI_ROW_DELIMITER);
if (index <= 0) {
continue;
}
if (key.length() > ROW_SUBMIT_PREFIX_LENGTH && key.substring(0, ROW_SUBMIT_PREFIX_LENGTH).equals(ROW_SUBMIT_PREFIX)) {
continue;
}
// get the map with index N
Integer n = Integer.decode(key.substring(index + MULTI_ROW_DELIMITER_LENGTH, key.length())); // N from ${param}${DELIMITER}${N}
Map<String, Object> map = rows.get(n);
if (map == null) {
continue;
}
// get the key without the <DELIMITER>N suffix and store it and its value
String newKey = key.substring(0, index);
map.put(newKey, entry.getValue());
}
// return only the values, which is the list of maps
return rows.values();
}
/**
* Returns a new map containing all the parameters from the input map except for the
* multi form parameters (usually named according to the ${param}_o_N notation).
*/
public static <V> Map<String, V> removeMultiFormParameters(Map<String, V> parameters) {
Map<String, V> filteredParameters = new HashMap<>();
for (Map.Entry<String, V> entry : parameters.entrySet()) {
String key = entry.getKey();
if (key != null && (key.indexOf(MULTI_ROW_DELIMITER) != -1 || key.indexOf("_useRowSubmit") != -1 || key.indexOf("_rowCount") != -1)) {
continue;
}
filteredParameters.put(key, entry.getValue());
}
return filteredParameters;
}
/**
* Utility to make a composite parameter from the given prefix and suffix.
* The prefix should be a regular parameter name such as meetingDate. The
* suffix is the composite field, such as the hour of the meeting. The
* result would be meetingDate_${COMPOSITE_DELIMITER}_hour.
* @param prefix
* @param suffix
* @return the composite parameter
*/
public static String makeCompositeParam(String prefix, String suffix) {
return prefix + COMPOSITE_DELIMITER + suffix;
}
/**
* Assembles a composite object from a set of parameters identified by a common prefix.
* <p>
* For example, consider the following form widget field:
* <pre>
* {@code
* <field name="meetingDate">
* <date-time type="timestamp" input-method="time-dropdown">
* </field>
* }
* </pre>
* The HTML result is three input boxes to input the date, hour and minutes separately.
* The parameter names are named {@code meetingDate_c_date}, {@code meetingDate_c_hour},
* {@code meetingDate_c_minutes}. Additionally, there will be a field named {@code meetingDate_c_compositeType}
* with a value of "Timestamp". where "_c_" is the {@link #COMPOSITE_DELIMITER}. These parameters will then be
* re-composed into a Timestamp object from the composite fields.
* @param request the HTTP request containing the parameters
* @param prefix the string identifying the set of parameters that must be composed
* @return a composite object from data or {@code null} if not supported or a parsing error occurred.
*/
public static Object makeParamValueFromComposite(HttpServletRequest request, String prefix) {
String compositeType = request.getParameter(makeCompositeParam(prefix, "compositeType"));
if (UtilValidate.isEmpty(compositeType)) {
return null;
}
// Collect the components.
String prefixDelim = prefix + COMPOSITE_DELIMITER;
Map<String, String> data = request.getParameterMap().entrySet().stream()
.filter(e -> e.getKey().startsWith(prefixDelim))
.collect(Collectors.toMap(
e -> e.getKey().substring(prefixDelim.length()),
e -> e.getValue()[0]));
if (Debug.verboseOn()) {
Debug.logVerbose("Creating composite type with parameter data: " + data.toString(), MODULE);
}
// Assemble the composite data from the components
if ("Timestamp".equals(compositeType)) {
String date = data.get("date");
String hour = data.get("hour");
String minutes = data.get("minutes");
String ampm = data.get("ampm");
if (date == null || date.length() < 10 || UtilValidate.isEmpty(hour) || UtilValidate.isEmpty(minutes)) {
return null;
}
try {
int h = Integer.parseInt(hour);
Timestamp ts = Timestamp.valueOf(date.substring(0, 10) + " 00:00:00.000");
if (UtilValidate.isNotEmpty(ampm)) {
boolean isAM = "AM".equals(ampm);
if (isAM && h == 12) {
h = 0;
} else if (!isAM && h < 12) {
h += 12;
}
}
LocalDateTime ldt = ts.toLocalDateTime().withHour(h).withMinute(Integer.parseInt(minutes));
return Timestamp.valueOf(ldt);
} catch (IllegalArgumentException e) {
Debug.logWarning("User input for composite timestamp was invalid: " + e.getMessage(), MODULE);
return null;
}
}
// we don't support any other compositeTypes (yet)
return null;
}
/**
* Obtains the session ID from the request, or "unknown" if no session pressent.
*/
public static String getSessionId(HttpServletRequest request) {
HttpSession session = request.getSession();
return (session == null ? "unknown" : session.getId());
}
/**
* Returns true if the user has JavaScript enabled.
* @param request
* @return whether javascript is enabled
*/
public static boolean isJavaScriptEnabled(HttpServletRequest request) {
HttpSession session = request.getSession();
Boolean javaScriptEnabled = (Boolean) session.getAttribute("javaScriptEnabled");
return javaScriptEnabled != null ? javaScriptEnabled : false;
}
/**
* Returns the number or rows submitted by a multi form.
*/
public static int getMultiFormRowCount(HttpServletRequest request) {
return getMultiFormRowCount(getParameterMap(request));
}
/**
* Returns the number or rows submitted by a multi form.
*/
public static int getMultiFormRowCount(Map<String, ?> requestMap) {
// The number of multi form rows is computed selecting the maximum index
int rowCount = 0;
String maxRowIndex = "";
int rowDelimiterLength = MULTI_ROW_DELIMITER.length();
for (String parameterName : requestMap.keySet()) {
int rowDelimiterIndex = (parameterName != null ? parameterName.indexOf(MULTI_ROW_DELIMITER) : -1);
if (rowDelimiterIndex > 0) {
String thisRowIndex = parameterName.substring(rowDelimiterIndex + rowDelimiterLength);
if (thisRowIndex.indexOf("_") > -1) {
thisRowIndex = thisRowIndex.substring(0, thisRowIndex.indexOf("_"));
}
if (maxRowIndex.length() < thisRowIndex.length()) {
maxRowIndex = thisRowIndex;
} else if (maxRowIndex.length() == thisRowIndex.length() && maxRowIndex.compareTo(thisRowIndex) < 0) {
maxRowIndex = thisRowIndex;
}
}
}
if (UtilValidate.isNotEmpty(maxRowIndex)) {
try {
rowCount = Integer.parseInt(maxRowIndex);
rowCount++; // row indexes are zero based
} catch (NumberFormatException e) {
Debug.logWarning("Invalid value for row index found: " + maxRowIndex, MODULE);
}
}
return rowCount;
}
public static String stashParameterMap(HttpServletRequest request) {
HttpSession session = request.getSession();
Map<String, Map<String, Object>> paramMapStore = UtilGenerics.cast(session.getAttribute("_PARAM_MAP_STORE_"));
if (paramMapStore == null) {
paramMapStore = new HashMap<>();
session.setAttribute("_PARAM_MAP_STORE_", paramMapStore);
}
Map<String, Object> parameters = getParameterMap(request);
String paramMapId = RandomStringUtils.randomAlphanumeric(10);
paramMapStore.put(paramMapId, parameters);
return paramMapId;
}
public static void restoreStashedParameterMap(HttpServletRequest request, String paramMapId) {
HttpSession session = request.getSession();
Map<String, Map<String, Object>> paramMapStore = UtilGenerics.cast(session.getAttribute("_PARAM_MAP_STORE_"));
if (paramMapStore != null) {
Map<String, Object> paramMap = paramMapStore.get(paramMapId);
if (paramMap != null) {
paramMapStore.remove(paramMapId);
for (Map.Entry<String, Object> paramEntry : paramMap.entrySet()) {
if (request.getAttribute(paramEntry.getKey()) != null) {
Debug.logWarning("Skipped loading parameter [" + paramEntry.getKey() + "] because it would have overwritten a request "
+ "attribute", MODULE);
continue;
}
request.setAttribute(paramEntry.getKey(), paramEntry.getValue());
}
}
}
}
/**
* Returns a unique Id for the current request
* @param request An HttpServletRequest to get the name info from
* @return String
*/
public static String getNextUniqueId(HttpServletRequest request) {
Integer uniqueIdNumber = (Integer) request.getAttribute("UNIQUE_ID");
if (uniqueIdNumber == null) {
uniqueIdNumber = 1;
}
request.setAttribute("UNIQUE_ID", uniqueIdNumber + 1);
return "autoId_" + uniqueIdNumber;
}
private static void setContentDisposition(final HttpServletResponse response, final String filename) {
String dispositionType = UtilProperties.getPropertyValue("requestHandler", "content-disposition-type", "attachment");
response.setHeader("Content-Disposition", String.format("%s; filename=\"%s\"", dispositionType, filename));
}
public static CloseableHttpClient getAllowAllHttpClient() {
return getAllowAllHttpClient("component://base/config/ofbizssl.jks", "changeit");
}
private static CloseableHttpClient getAllowAllHttpClient(String jksStoreFileName, String jksStorePassword) {
try {
// Trust own CA and all self-signed certs
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(FileUtil.getFile(jksStoreFileName), jksStorePassword.toCharArray(),
new TrustSelfSignedStrategy())
.build();
// No host name verifier
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext,
NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
return httpClient;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
return HttpClients.createDefault();
}
}
public static String getMultiRowDelimiter() {
return MULTI_ROW_DELIMITER;
}
public static String getRowSubmitPrefix() {
return ROW_SUBMIT_PREFIX;
}
// From https://stackoverflow.com/questions/1806017/extracting-urls-from-a-text-document-using-java-regular-expressions/1806161#answer-1806161
// If you need more Internet top-level domains: https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains
public static List<String> extractUrls(String input) {
List<String> result = new ArrayList<String>();
Pattern pattern = Pattern.compile(
"\\b(((ht|f)tp(s?)\\:\\/\\/|~\\/|\\/)|www.)"
+ "(\\w+:\\w+@)?(([-\\w]+\\.)+(com|org|net|gov"
+ "|mil|biz|info|mobi|name|aero|jobs|museum"
+ "|travel|[a-z]{2}))(:[\\d]{1,5})?"
+ "(((\\/([-\\w~!$+|.,=]|%[a-f\\d]{2})+)+|\\/)+|\\?|#)?"
+ "((\\?([-\\w~!$+|.,*:]|%[a-f\\d{2}])+=?"
+ "([-\\w~!$+|.,*:=]|%[a-f\\d]{2})*)"
+ "(&(?:[-\\w~!$+|.,*:]|%[a-f\\d{2}])+=?"
+ "([-\\w~!$+|.,*:=]|%[a-f\\d]{2})*)*)*"
+ "(#([-\\w~!$+|.,*:=]|%[a-f\\d]{2})*)?\\b");
List<String> allowedProtocols = getAllowedProtocols();
for (String protocol : allowedProtocols) {
if (input.contains(protocol)) {
result.add(input);
}
}
if (result.isEmpty()) {
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
result.add(matcher.group());
}
}
return result;
}
private static List<String> getAllowedProtocols() {
List<String> allowedProtocolList = new LinkedList<>();
allowedProtocolList.add("component://");
String allowedProtocols = UtilProperties.getPropertyValue("security", "allowedProtocols");
if (UtilValidate.isNotEmpty(allowedProtocols)) {
List<String> allowedProtocolsList = StringUtil.split(allowedProtocols, ",");
for (String protocol : allowedProtocolsList) {
allowedProtocolList.add(protocol);
}
}
return allowedProtocolList;
}
}