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