TransportUtils.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.openspcoop2.utils.transport;

import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.openspcoop2.utils.LoggerWrapperFactory;
import org.openspcoop2.utils.UtilsRuntimeException;
import org.openspcoop2.utils.resources.Charset;
import org.slf4j.Logger;
import org.springframework.web.util.UriUtils;

/**
 * TransportUtils
 *
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class TransportUtils {
	
	private TransportUtils() {}

	public static final String LDAP_PREFIX = "ldap://";
	public static final String HTTP_PREFIX = "http://";
	public static final String HTTPS_PREFIX = "https://";
	public static final String FILE_PREFIX = "file://";
	public static boolean isRemoteResource(String r) {
		return r!=null && 
				(r.trim().startsWith(LDAP_PREFIX) 
						||
						r.trim().startsWith(HTTP_PREFIX) 
						||
						r.trim().startsWith(HTTPS_PREFIX) 
						||
						r.trim().startsWith(FILE_PREFIX) );
	}
	public static boolean isLdapResource(String r) {
		return r!=null && 
				(r.trim().startsWith(LDAP_PREFIX) );
	}
	public static boolean isHttpResource(String r) {
		return r!=null && 
				(r.trim().startsWith(HTTP_PREFIX) );
	}
	public static boolean isHttpsResource(String r) {
		return r!=null && 
				(r.trim().startsWith(HTTPS_PREFIX) );
	}
	public static boolean isFileResource(String r) {
		return r!=null && 
				(r.trim().startsWith(FILE_PREFIX) );
	}
	
	/* Gestione CaseInsensitive per Properties */

	public static boolean containsKey(Map<String, List<String>> p, String name) {
		return hasKeyEngine(p, name);
	}
	@Deprecated
	public static boolean hasKey(Map<String, String> p, String name) {
		return hasKeyEngine(p, name);
	}
	private static boolean hasKeyEngine(Map<String, ?> p, String name) {
		
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		if(p==null || p.isEmpty()) {
			return false;
		}
		if(name==null) {
			return false;
		}
		if(p.containsKey(name)) {
			return true;
		}
		String lowerName = name.toLowerCase();
		if(p.containsKey(lowerName)) {
			return true;
		}
		if(p.containsKey(name.toUpperCase())) {
			return true;
		}
		Iterator<String> keys = p.keySet().iterator();
		while (keys.hasNext()) {
			String key = keys.next();
			String keyCaseInsensitive = key.toLowerCase();
			if(keyCaseInsensitive.equals( lowerName )) {
				return true;
			}
		}
		return false;
		
	}
	
	// Usare i metodi sottostanti come getRawObject e getObjectAsString
	@Deprecated
	public static String get(Map<String, String> p, String name) {
		Object o = propertiesEngine(p, name, true);
		return (o instanceof String) ? ((String)o) : null;
	}
	@Deprecated
	public static Object remove(Map<String, String> p, String name) {
		return propertiesEngine(p, name, false);
	}
	private static <T> T propertiesEngine(Map<String, T> p, String name, boolean get) {
		
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		if(p==null || p.isEmpty()) {
			return null;
		}
		if(name==null) {
			return null;
		}
		T value = get ? p.get(name) : p.remove(name);
		String lowerName = null;
		if(value==null){
			lowerName = name.toLowerCase();
			value = get ? p.get( lowerName ) : p.remove( lowerName );
		}
		if(value==null){
			value = get ? p.get(name.toUpperCase()) : p.remove(name.toUpperCase()); 
		}
		if(value==null){
			List<String> keysFound = new ArrayList<>();
			Iterator<String> keys = p.keySet().iterator();
			while (keys.hasNext()) {
				String key = keys.next();
				String keyCaseInsensitive = key.toLowerCase();
				if ( lowerName == null )
					lowerName = name.toLowerCase();
				String nameCaseInsensitive = lowerName;
				if(keyCaseInsensitive.equals(nameCaseInsensitive)) {
					keysFound.add(key);
				}
			}
			if(!keysFound.isEmpty()) {
				for (String keyFound : keysFound) {
					T v = get? p.get(keyFound) : p.remove(keyFound);
					if(value==null) {
						value=v;
					}
				}
			}
		}
		return value;
		
	}
	
	
	
	/* Gestione CaseInsensitive per Map */
	
	public static boolean mapHasKey(Map<String, Object> map, String name) {
		
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		if(map==null || map.isEmpty()) {
			return false;
		}
		if(name==null) {
			return false;
		}
		if(map.containsKey(name)) {
			return true;
		}
		String lowerName = name.toLowerCase();
		if(map.containsKey( lowerName )) {
			return true;
		}
		if(map.containsKey(name.toUpperCase())) {
			return true;
		}
		Iterator<String> keys = map.keySet().iterator();
		while (keys.hasNext()) {
			String key = keys.next();
			if(key!=null) {
				String keyCaseInsensitive = key.toLowerCase();
				if(keyCaseInsensitive.equals( lowerName )) {
					return true;
				}
			}
		}
		return false;
		
	}
	public static String getObjectAsString(Map<String, ?> map, String name) {
		return (String) mapEngine(map, name, true, true);
	}
	public static String removeObjectAsString(Map<String, ?> map, String name) {
		return (String) mapEngine(map, name, false, true);
	}
	@SuppressWarnings("unchecked")
	public static <T> T getRawObject(Map<String, T> map, String name) {
		return (T) mapEngine(map, name, true, false);
	}
	@SuppressWarnings("unchecked")
	public static <T> T removeRawObject(Map<String, T> map, String name) {
		return (T) mapEngine(map, name, false, false);
	}
	public static void removeObject(Map<String, ?> map, String name) {
		mapEngine(map, name, false, false);
	}
	private static Object mapEngine(Map<String, ?> map, String name, boolean get, boolean returnAsString) {
		
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		if(map==null || map.isEmpty()) {
			return null;
		}
		if(name==null) {
			return null;
		}
		String lowerName = null;
		Object value = get? map.get(name) : map.remove(name);
		if(value==null){
			lowerName = name.toLowerCase();
			value = get? map.get( lowerName ) : map.remove( lowerName );
		}
		if(value==null){
			value = get? map.get(name.toUpperCase()) : map.remove(name.toUpperCase());
		}
		if(value==null){
			List<String> keysFound = new ArrayList<>();
			Iterator<String> keys = map.keySet().iterator();
			while (keys.hasNext()) {
				String key = keys.next();
				if(key!=null) {
					String keyCaseInsensitive = key.toLowerCase();
					if ( lowerName == null )
						lowerName = name.toLowerCase();
					String nameCaseInsensitive = lowerName;
					if(keyCaseInsensitive.equals(nameCaseInsensitive)) {
						keysFound.add(key);
					}
				}
			}
			if(!keysFound.isEmpty()) {
				for (String keyFound : keysFound) {
					Object v = get? map.get(keyFound) : map.remove(keyFound);
					if(value==null) {
						value=v;
					}
				}
			}
		}
		if(value==null) {
			return null;
		}
		if(!returnAsString) {
			return value; 
		}
		else {
			if(value instanceof String) {
				return value;
			}
			else if(value instanceof List<?>) {
				List<?> l = (List<?>) value;
				return convertToSingleValue(l);
			}
			else {
				return value.toString();
			}
		}
		
	}
	
	
	
	/* HttpServlet */
	
	@Deprecated
	public static String getParameter(HttpServletRequest request, String name) {
		return getParameterFirstValue(request, name);
	}
	public static String getParameterFirstValue(HttpServletRequest request, String name) {
		List<String> l = getParameterValues(request, name);
		if(l!=null) {
			return l.get(0);
		}
		return null;
	}
	public static List<String> getParameterValues(HttpServletRequest request, String name) {
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		List<String> lNullReturn = null;
		
		if(request==null) {
			return lNullReturn;
		}
		if(name==null) {
			return lNullReturn;
		}
		String lowerName = null;
		String exactKeyName = null;
		Enumeration<String> keys = request.getParameterNames();
		if(keys!=null) {
			while (keys.hasMoreElements()) {
				String key = keys.nextElement();
				String keyCaseInsensitive = key.toLowerCase();
				if ( lowerName == null )
					lowerName = name.toLowerCase();
				String nameCaseInsensitive = lowerName;
				if(keyCaseInsensitive.equals(nameCaseInsensitive)) {
					exactKeyName = key;
					break;
				}
			}
		}
		if(exactKeyName==null) {
			return lNullReturn;
		}
		
		String [] s = request.getParameterValues(exactKeyName);
		List<String> values = new ArrayList<>();
		if(s!=null && s.length>0) {
			for (int i = 0; i < s.length; i++) {
				String value = s[i];
				values.add(value);
				/**System.out("Parameter ["+nomeProperty+"] valore-"+i+" ["+value+"]");*/
			}
		}
		else {
			/**System.out("Parameter ["+nomeProperty+"] valore ["+req.getParameter(nomeProperty)+"]");*/
			values.add(request.getParameter(exactKeyName));
		}
		
		return values;
		
	}
	
	@Deprecated
	public static String getHeader(HttpServletRequest request, String name) {
		return getHeaderFirstValue(request, name);
	}
	public static String getHeaderFirstValue(HttpServletRequest request, String name) {
		List<String> l = getHeaderValues(request, name);
		if(l!=null) {
			return l.get(0);
		}
		return null;
	}
	public static List<String> getHeaderValues(HttpServletRequest request, String name) {
		
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		List<String> lNullReturn = null;
		
		if(request==null) {
			return lNullReturn;
		}
		if(name==null) {
			return lNullReturn;
		}
		String lowerName = null;
		String exactKeyName = null;
		Enumeration<String> keys = request.getHeaderNames();
		if(keys!=null) {
			while (keys.hasMoreElements()) {
				String key = keys.nextElement();
				String keyCaseInsensitive = key.toLowerCase();
				if ( lowerName == null )
					lowerName = name.toLowerCase();
				String nameCaseInsensitive = lowerName;
				if(keyCaseInsensitive.equals(nameCaseInsensitive)) {
					exactKeyName = key;
					break;
				}
			}
		}
		if(exactKeyName==null) {
			return lNullReturn;
		}
		
		Enumeration<String> enValues = request.getHeaders(exactKeyName);
		List<String> values = new ArrayList<>();
		if(enValues!=null) {
			@SuppressWarnings("unused")
			int i = 0;
			while (enValues.hasMoreElements()) {
				String value = enValues.nextElement();
				values.add(value);
				/**System.out("Header ["+nomeHeader+"] valore-"+i+" ["+value+"]");*/
				i++;
			}
		}
		if(values.isEmpty()) {
			/**System.out("Header ["+nomeHeader+"] valore ["+req.getHeader(nomeHeader)+"]");*/
			values.add(request.getHeader(exactKeyName));
		}
		
		return values;
		
	}
	
	@Deprecated
	public static String getHeader(HttpServletResponse response, String name) {
		return getHeaderFirstValue(response, name);
	}
	public static String getHeaderFirstValue(HttpServletResponse response, String name) {
		List<String> l = getHeaderValues(response, name);
		if(l!=null) {
			return l.get(0);
		}
		return null;
	}
	public static List<String> getHeaderValues(HttpServletResponse response, String name) {
				
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		List<String> lNullReturn = null;
		
		if(response==null) {
			return lNullReturn;
		}
		if(name==null) {
			return lNullReturn;
		}
		String lowerName = null;
		String exactKeyName = null;
		if(response.getHeaderNames()!=null && !response.getHeaderNames().isEmpty()) {
			Iterator<String> keys = response.getHeaderNames().iterator();
			if(keys!=null) {
				while (keys.hasNext()) {
					String key = keys.next();
					String keyCaseInsensitive = key.toLowerCase();
					if ( lowerName == null )
						lowerName = name.toLowerCase();
					String nameCaseInsensitive = lowerName;
					if(keyCaseInsensitive.equals(nameCaseInsensitive)) {
						exactKeyName = key;
					}
				}
			}
		}
		if(exactKeyName==null) {
			return lNullReturn;
		}
		
		Collection<String> enValues = response.getHeaders(exactKeyName);
		List<String> values = new ArrayList<>();
		if(enValues!=null && !enValues.isEmpty()) {
			@SuppressWarnings("unused")
			int i = 0;
			for (String value : enValues) {
				values.add(value);
				/**System.out("Header ["+nomeHeader+"] valore-"+i+" ["+value+"]");*/
				i++;
			}
		}
		if(values.isEmpty()) {
			/**System.out("Header ["+nomeHeader+"] valore ["+req.getHeader(nomeHeader)+"]");*/
			values.add(response.getHeader(exactKeyName));
		}
		
		return values;
		
	}
	
	public static String getCookie(HttpServletRequest request, String name) {
		
		// rfc7230#page-22: Each header field consists of a case-insensitive field name followed by a colon (":")
		
		if(request==null) {
			return null;
		}
		if(name==null) {
			return null;
		}
		Cookie[] c = request.getCookies();
		if(c==null || c.length<=0) {
			return null;
		}
		for (Cookie cookie : c) {
			if(cookie!=null && cookie.getName()!=null) {
				if(cookie.getName().equalsIgnoreCase(name)) {
					try {
						return URLDecoder.decode(cookie.getValue(), Charset.UTF_8.getValue());
					}catch(Exception e) {
						throw new UtilsRuntimeException(e.getMessage(),e);
					}
				}
			}
		}
		return null;

	}
	
	
	
	
	
	/* Gestione URL */
	
	@Deprecated
	public static String buildLocationWithURLBasedParameter(Map<String, String> propertiesURLBased, String location){
		return buildLocationWithURLBasedParameter(propertiesURLBased, location, false, LoggerWrapperFactory.getLogger(TransportUtils.class));
	}
	@Deprecated
	public static String buildLocationWithURLBasedParameter(Map<String, String> propertiesURLBased, String location, Logger log){
		return buildLocationWithURLBasedParameter(propertiesURLBased, location, false, log);
	}
	@Deprecated
	public static String buildLocationWithURLBasedParameter(Map<String, String> propertiesURLBased, String location, boolean encodeLocation){
		return buildLocationWithURLBasedParameter(propertiesURLBased, location, encodeLocation, LoggerWrapperFactory.getLogger(TransportUtils.class));
	}
	@Deprecated
	public static String buildLocationWithURLBasedParameter(Map<String, String> propertiesURLBased, String location, boolean encodeLocation, Logger log){
		Map<String, List<String>> parameters = convertToMapListValues(propertiesURLBased);
		return buildUrlWithParameters(parameters, location, encodeLocation, log);
	}
	
	public static String buildUrlWithParameters(Map<String, List<String>> parameters, String location){
		return buildUrlWithParameters(parameters, location, false, LoggerWrapperFactory.getLogger(TransportUtils.class));
	}
	public static String buildUrlWithParameters(Map<String, List<String>> parameters, String location, Logger log){
		return buildUrlWithParameters(parameters, location, false, log);
	}
	public static String buildUrlWithParameters(Map<String, List<String>> parameters, String location, boolean encodeLocation){
		return buildUrlWithParameters(parameters, location, encodeLocation, LoggerWrapperFactory.getLogger(TransportUtils.class));
	}
	public static String buildUrlWithParameters(Map<String, List<String>> parameters, String location, boolean encodeLocation, Logger log){
		String locationEncoded = location;
		if(encodeLocation) {
			locationEncoded = UriUtils.encodeQuery(location,Charset.UTF_8.getValue());
		}
		
		if(parameters != null && parameters.size()>0){
			StringBuilder urlBuilder = new StringBuilder(locationEncoded);
			Iterator<String> keys = parameters.keySet().iterator();
			while (keys.hasNext()) {
				
				String key = keys.next();
				List<String> list = parameters.get(key);
				if(list!=null && !list.isEmpty()) {
				
					for (String value : list) {
						
						if(!urlBuilder.toString().contains("?"))
							urlBuilder.append("?");
						else
							urlBuilder.append("&");
						
						try{
							key = urlEncodeParam(key,Charset.UTF_8.getValue());
						}catch(Exception e){
							if(log!=null) {
								log.error("URLEncode key["+key+"] error: "+e.getMessage(),e);
							}
							else {
								LoggerWrapperFactory.getLogger(TransportUtils.class).error("URLEncode key["+key+"] error: "+e.getMessage(),e);
							}
						}
						
						try{
							value = urlEncodeParam(value,Charset.UTF_8.getValue());
						}catch(Exception e){
							if(log!=null) {
								log.error("URLEncode value:["+value+"] error: "+e.getMessage(),e);
							}
							else {
								LoggerWrapperFactory.getLogger(TransportUtils.class).error("URLEncode value:["+value+"] error: "+e.getMessage(),e);
							}
						}
						
						String keyValue = key+"="+value;
						urlBuilder.append(keyValue);
						
					}
					
				}
			}
			return urlBuilder.toString();
		}
		else{
			return locationEncoded;
		}
	}

	public static String limitLocation255Character(String location){
		if(location.length()>255){
			return location.substring(0,251)+" ...";
		}
		else{
			return location;
		}
	}
	
	
	public static String urlEncodeParam(String value, String charset) {
		
		// Note that Java’s URLEncoder class encodes space character(" ") into a + sign. 
		// This is contrary to other languages like Javascript that encode space character into %20.
		
		/*
		 *  URLEncoder is not for encoding URLs, but for encoding parameter names and values for use in GET-style URLs or POST forms. 
		 *  That is, for transforming plain text into the application/x-www-form-urlencoded MIME format as described in the HTML specification. 
		 **/
				
		/**return java.net.URLEncoder.encode(value,"UTF8");*/
		
		// fix
		String p = UriUtils.encodeQueryParam(value, charset);
		int index = 0;
		while(p.contains("+") && index<1000) {
			p = p.replace("+", "%2B");
			index++;
		}
		return p;
	}
	
	public static String urlEncodePath(String value, String charset) {
		return UriUtils.encode(value, charset);
	}
	
	
	/* ************** UTILITIES *************/
	
	public static String convertToSingleValue(List<?> l) {
		if(l!=null && !l.isEmpty()) {
			StringBuilder sb = new StringBuilder();
			for (Object value : l) {
				if(value==null) {
					continue;
				}
				if(sb.length()>0) {
					sb.append(",");
				}
				sb.append(value);
			}
			if(sb.length()>0) {
				return sb.toString();
			}
			else {
				return null;
			}
		}
		return null;
	}
	
	public static Map<String, List<String>> convertToMapListValues(Map<String, String> mapSingleValue) {
		Map<String, List<String>> mapMultipleValues = null;
		if(mapSingleValue!=null && !mapSingleValue.isEmpty()) {
			mapMultipleValues = new HashMap<>();
			Iterator<String> keys = mapSingleValue.keySet().iterator();
			while (keys.hasNext()) {
				String key = keys.next();
				String value = mapSingleValue.get(key);
				List<String> l = new ArrayList<>();
				l.add(value);
				mapMultipleValues.put(key, l);
			}
		}
		return mapMultipleValues;
	}
	
	public static Map<String, String> convertToMapSingleValue(Map<String, List<String>> mapMultipleValues) {
		Map<String, String> mapSingleValue = null;
		if(mapMultipleValues!=null && !mapMultipleValues.isEmpty()) {
			mapSingleValue = new HashMap<>();
			Iterator<String> keys = mapMultipleValues.keySet().iterator();
			while (keys.hasNext()) {
				String key = keys.next();
				List<String> values = mapMultipleValues.get(key);
				String v = convertToSingleValue(values);
				if(v!=null) {
					mapSingleValue.put(key, v);
				}
			}
		}
		return mapSingleValue;
	}
	
	public static void addHeader(Map<String, List<String>> headers, String name, String value) {
		put(headers, name, value, true);
	}
	public static void setHeader(Map<String, List<String>> headers, String name, String value) {
		put(headers, name, value, false);
	}
	
	public static void addParameter(Map<String, List<String>> parameters, String name, String value) {
		put(parameters, name, value, true);
	}
	public static void setParameter(Map<String, List<String>> parameters, String name, String value) {
		put(parameters, name, value, false);
	}
	
	public static void put(Map<String, List<String>> map, String name, String value, boolean add) {
		
		if(value==null) {
			return;
		}
		
		List<String> l = map.get(name);
		if(add) {
			if(l==null) {
				l = new ArrayList<>();
				map.put(name, l);
			}
		}
		else {
			if(l==null) {
				l = new ArrayList<>();
				map.put(name, l);
			}
			else {
				l.clear();
			}
		}
		l.add(value);
	}
	
	
	
	public static List<String> getValues(Map<String, List<String>> map, String key) {
		List<String> lReturn = null;
		if(map!=null && !map.isEmpty()) {
			List<String> l = getRawObject(map, key); 
			if(l!=null && !l.isEmpty()) {
				return l;
			}
		}
		return lReturn;
	}
	
	public static String getFirstValue(Map<String, List<String>> map, String key) {
		if(map!=null && !map.isEmpty()) {
			List<String> l = getRawObject(map, key); 
			if(l!=null && !l.isEmpty()) {
				return l.get(0);
			}
		}
		return null;
	}
	public static String getFirstValue(List<String> list) {
		if(list!=null && !list.isEmpty()) {
			return list.get(0);
		}
		return null;
	}
}