ContentTypeUtilities.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.http;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.soap.SOAPException;

import org.openspcoop2.utils.Utilities;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.mime.MultipartUtils;
import org.openspcoop2.utils.regexp.RegExpNotFoundException;
import org.openspcoop2.utils.regexp.RegularExpressionEngine;
import org.slf4j.Logger;

//import javax.mail.internet.ContentType;
//import javax.mail.internet.ParameterList;
import com.sun.xml.messaging.saaj.packaging.mime.internet.ContentType;
import com.sun.xml.messaging.saaj.packaging.mime.internet.ParameterList;



/**
 * ContentTypeUtilities
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class ContentTypeUtilities {
	
	
	
	// Utilities generiche
	
	public static void validateContentType(String ct) throws UtilsException{
		try {
			if(ct!=null && !"".equals(ct)) {
				
				(new javax.mail.internet.ContentType(ct)).getBaseType(); // uso javax.mail per validare, restituisce un errore migliore
				
				if(ContentTypeUtilities.isMultipart(ct)){
					String internal = ContentTypeUtilities.getInternalMultipartContentType(ct);
					if(internal!=null){
						ContentTypeUtilities.isMtom(internal);
					}
				}
			}
		}catch(Throwable e) {
			String msgError = e.getMessage();
			if(msgError==null || "".equals(msgError) || "null".equals(msgError)) {
				msgError = Utilities.getInnerNotEmptyMessageException(e).getMessage();
			}
			if(msgError==null || "".equals(msgError) || "null".equals(msgError)) {
				msgError = "Parsing failed";
			}
			throw new UtilsException(msgError,e);
		}
	}
	
	public static String buildContentType(String baseType,Map<String, String> parameters) throws UtilsException{
		try{
			ContentType cType = new ContentType(baseType);
			if(parameters!=null && parameters.size()>0){
				Iterator<String> itP = parameters.keySet().iterator();
				while (itP.hasNext()) {
					String parameterName = (String) itP.next();
					String parameterValue = parameters.get(parameterName);
					if(cType.getParameterList()==null){
						cType.setParameterList(new ParameterList());
					}
					cType.getParameterList().remove(parameterName);
					cType.getParameterList().set(parameterName, parameterValue);
				}
				
			}
			
			/*
			 * //import javax.mail.internet.ContentType;
				//import javax.mail.internet.ParameterList;

				import com.sun.xml.messaging.saaj.packaging.mime.internet.ContentType;
				import com.sun.xml.messaging.saaj.packaging.mime.internet.ParameterList;
				
				Utilizzo la versione saaj poiche il toString di javax.mail.internet.ContentType in presenza di un valore con ':' non funziona correttamente e genera valori action*0 e action*1
			 * 
			 * */
			
			String ct = cType.toString(); // il toString in presenza di action con valore http://... non funziona correttamente e genera valori action*0 e action*1
			
			// Reimplementare il toString non basta poiche' i ':' fanno schiantare un successivo parser del javax.mail.internet.ContentType
//			StringBuilder ctBufferParam = new StringBuilder();
//			ParameterList pList = cType.getParameterList();
//			if(pList!=null && pList.size()>0) {
//				java.util.Enumeration<String> en = pList.getNames();
//				while (en.hasMoreElements()) {
//					String name = (String) en.nextElement();
//					ctBufferParam.append("; ");
//					ctBufferParam.append(name).append("=").append(pList.get(name));
//				}
//			}
//			String ct = cType.getBaseType();
//			if(ctBufferParam.length()>0) {
//				ct = ct + ctBufferParam.toString();
//			}
			ct = normalizeToRfc7230(ct);			
			ct = ct.trim();
			return ct;
		}catch(Exception e){
			throw new RuntimeException("Error during buildContentType: "+e.getMessage(), e);
		}
	}
	
	public static String normalizeToRfc7230(String ct) {
		// Line folding of headers has been deprecated in the latest HTTP RFC:
		// http://tools.ietf.org/html/rfc7230#section-3.2.4
		while (ct.contains("\r\n")) {
			ct = ct.replace("\r\n", " ");
		}
		while (ct.contains("\r")) {
			ct = ct.replace("\r", " ");
		}
		while (ct.contains("\n")) {
			ct = ct.replace("\n", " ");
		}
		while (ct.contains("\t")) {
			ct = ct.replace("\t", " ");
		}
		return ct.trim();
	}
	
	public static String readBaseTypeFromContentType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			return contentType.getBaseType();
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public static String readPrimaryTypeFromContentType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			return contentType.getPrimaryType();
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public static String readSubTypeFromContentType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			return contentType.getSubType();
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	public static String readCharsetFromContentType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			String charsetParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_PARAMETER_CHARSET); 
			if (charsetParam != null) {
				return charsetParam.trim();
			}
			return null;
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	
	
	// match
	
	public static boolean isMatch(Logger logNullable, String contentTypeParam, String contentTypeAtteso) throws UtilsException {
		List<String> l = new ArrayList<>();
		l.add(contentTypeAtteso);
		return isMatch(logNullable, contentTypeParam, l);
	}
	public static boolean isMatch(Logger logNullable, String contentTypeParam, List<String> contentTypeAttesi) throws UtilsException {
		
		if(contentTypeAttesi==null || contentTypeAttesi.isEmpty()) {
			return true;
		}
		
		String baseTypeHttp = contentTypeParam!=null ? ContentTypeUtilities.readBaseTypeFromContentType(contentTypeParam) : null;
		
		boolean found = false;
		for (String checkContentType : contentTypeAttesi) {
			if("empty".equals(checkContentType)){
				if(baseTypeHttp==null || "".equals(baseTypeHttp)) {
					found = true;
					break;
				}
			}
			else {
				if(baseTypeHttp==null) {
					continue;
				}
				if(checkContentType==null || "".equals(checkContentType) ||
						!checkContentType.contains("/") ||
						checkContentType.startsWith("/") ||
						checkContentType.endsWith("/")) {
					throw new UtilsException("Configurazione errata, content type indicato ("+checkContentType+") possiede un formato non corretto (atteso: type/subtype)");
				}
				String [] ctVerifica = checkContentType.split("/");
				String contentTypeEscaped = null;
				if(ctVerifica!=null && ctVerifica.length==2) {
					StringBuilder bf = new StringBuilder();
					String part1 = ctVerifica[0].trim();
					if("*".equals(part1)) {
						bf.append("(.+)");
					}
					else {
						// escape special char
						part1 = part1.replace("+", "\\+");
						bf.append(part1);
					}
					bf.append("/");
					String part2 = ctVerifica[1].trim();
					if("*".equals(part2)) {
						bf.append("(.+)");
					}
					else if(part2.startsWith("*")) {
						bf.append("(.+)");
						String sub = part2.substring(1);
						// escape special char
						sub = sub.replace("+", "\\+");
						bf.append(sub);
					}
					else {
						// escape special char
						part2 = part2.replace("+", "\\+");
						bf.append(part2);
					}
					contentTypeEscaped = bf.toString();
				}
				boolean isMatchEscaped = false; // gestisce le espressioni type/* e */*+xml
				boolean isMatchRegExp = false; // gestisce le espressioni regexpType/regexpSubType
				if(contentTypeEscaped!=null) {
					try {
						isMatchEscaped = RegularExpressionEngine.isMatch(baseTypeHttp, contentTypeEscaped);
					}catch(RegExpNotFoundException notFound) {
						// ignore
					}catch(Exception e) {
						throw new UtilsException(e.getMessage(),e);
					}
				}
				if(!isMatchEscaped) {
					try {
						isMatchRegExp = RegularExpressionEngine.isMatch(baseTypeHttp, checkContentType);
					}catch(RegExpNotFoundException notFound) {
						// ignore
					}catch(Exception e) {
						if(contentTypeEscaped==null) {
							throw new UtilsException(e.getMessage(),e);
						}
						else {
							// ignore
							// per evitare errori tipo:
							//org.openspcoop2.utils.UtilsException: Validazione del pattern indicato [*/*son] fallita: Dangling meta character '*' near index 0
							//                                                                        */*son
							//                                                                        ^
							if(logNullable!=null) {
								logNullable.debug("isMatch failed: "+e.getMessage(),e);
							}
						}
					}
				}
				if(isMatchEscaped || isMatchRegExp) {
					found = true;
					break;
				}
			}
			
		}
		return found;
	}
	
	
	
	
	// Utilities Multipart
	
	public static boolean isMultipartType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			String baseType = contentType.getBaseType();
			if(baseType!=null) {
				baseType = baseType.toLowerCase();
			}
			if(baseType!=null && baseType.startsWith(HttpConstants.CONTENT_TYPE_MULTIPART_TYPE+"/")){
				return true;
			}
			return false;
			
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	@Deprecated
	public static boolean isMultipart(String cType) throws UtilsException {
		return isMultipartRelated(cType);
	}
	public static boolean isMultipartRelated(String cType) throws UtilsException {
		return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_RELATED);
	}
	public static boolean isMultipartMixed(String cType) throws UtilsException {
		return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_MIXED);
	}
	public static boolean isMultipartAlternative(String cType) throws UtilsException {
		return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_ALTERNATIVE);
	}
	public static boolean isMultipartFormData(String cType) throws UtilsException {
		return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_FORM_DATA);
	}
	private static boolean _isMultipart(String cType, String expected) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			String baseType = contentType.getBaseType();
			if(baseType!=null) {
				baseType = baseType.toLowerCase();
			}
			if(baseType!=null && baseType.equals(expected)){
				return true;
			}
			return false;
			
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	public static boolean isMultipartContentType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			String baseType = contentType.getBaseType();
			if(baseType!=null) {
				baseType = baseType.toLowerCase();
			}
			if(baseType!=null && baseType.startsWith( (HttpConstants.CONTENT_TYPE_MULTIPART_TYPE+"/")) ){
				return true;
			}
			return false;
			
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	public static String getInternalMultipartContentType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			String baseType = contentType.getBaseType();
			if(baseType!=null) {
				baseType = baseType.toLowerCase();
			}
			String internalContentType = null;
			boolean mtom = false;
			if(baseType == null) {
				internalContentType = null;
			}
			else if(baseType.equals(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED)){
				String typeParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE); 
				if (typeParam != null) {
					internalContentType = typeParam.toLowerCase();
					if(HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML.equals(internalContentType)){
						mtom = true;
					}
				} 
			}
			else {
				internalContentType = baseType;
			}
			
			if(mtom) {
				internalContentType = readInternalMultipartMtomContentType(contentType);
			}
			
			return internalContentType;
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	@Deprecated
	public static String buildMultipartContentType(byte [] message, String type) throws UtilsException{
		return buildMultipartRelatedContentType(message, type);
	}
	public static String buildMultipartRelatedContentType(byte [] message, String type) throws UtilsException{
		return buildMultipartContentType(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED_SUBTYPE, message, type);
	}
	public static String buildMultipartContentType(String subtype, byte [] message, String type) throws UtilsException{
		if(MultipartUtils.messageWithAttachment(message)){
			String IDfirst  = MultipartUtils.firstContentID(message);
			return buildMultipartContentType(subtype, message, type, IDfirst);
		}
		throw new UtilsException("Messaggio non contiene una struttura mime");
	}
	@Deprecated
	public static String buildMultipartContentType(byte [] message, String type, String ID) throws UtilsException{
		return buildMultipartRelatedContentType(message, type, ID);
	}
	public static String buildMultipartRelatedContentType(byte [] message, String type, String ID) throws UtilsException{
		return buildMultipartContentType(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED_SUBTYPE, message, type, ID);
	}
	public static String buildMultipartContentType(String subtype, byte [] message, String type, String ID) throws UtilsException{
		if(MultipartUtils.messageWithAttachment(message)){
						
			String boundary = MultipartUtils.findBoundary(message);
			if(boundary==null){
				throw new UtilsException("Errore avvenuto durante la lettura del boundary associato al multipart message.");
			}
			StringBuilder bf = new StringBuilder();
			bf.append(HttpConstants.CONTENT_TYPE_MULTIPART_TYPE+"/"+subtype);
			if(type!=null){
				bf.append("; ").append(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE).append("=\"").append(type).append("\"");
			}
			if(ID!=null){
				bf.append("; ").append(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_START).append("=\"").append(ID).append("\"");
			}
			bf.append("; ").append(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_BOUNDARY).append("=\"").append(boundary.substring(2,boundary.length())).append("\"");
			
			return bf.toString();
		}
		throw new UtilsException("Messaggio non contiene una struttura mime");
	}
	
	
	
	
	// Utilities MTOM
	
	public static boolean isMtom(String cType) throws UtilsException{
		try{
			ContentType contentType = new ContentType(cType);
			String baseType = contentType.getBaseType();
			if(baseType!=null) {
				baseType = baseType.toLowerCase();
			}
			boolean mtom = false;
			if(baseType!=null && baseType.equals(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED)){
				String typeParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE); 
				if (typeParam == null) {
					throw new SOAPException("Missing '"+HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE+"' parameter in "+HttpConstants.CONTENT_TYPE_MULTIPART_RELATED);
				} else {
					String soapContentType = typeParam.toLowerCase();
					if(HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML.equals(soapContentType)){
						mtom = true;
					}
				} 
			}
			return mtom;
			
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
	
	public static String readInternalMultipartMtomContentType(String contentType) throws UtilsException{
		try{
			return readInternalMultipartMtomContentType(new ContentType(contentType));
		}
		catch(UtilsException e){
			throw e;
		}
		catch(Exception e){
			throw new UtilsException(e.getMessage(),e);
		}
	}
	public static String readInternalMultipartMtomContentType(ContentType contentType) throws UtilsException{
		try{
			if(contentType==null){
				throw new UtilsException("ContentType non fornito");
			}
			
			// baseType
			if(contentType.getBaseType()==null){
				throw new UtilsException("ContentType.baseType non definito");
			}
			if(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED.equals(contentType.getBaseType().toLowerCase())==false){
				throw new UtilsException("ContentType.baseType ["+contentType.getBaseType()+
						"] differente da quello atteso per un messaggio MTOM/XOP ["+HttpConstants.CONTENT_TYPE_MULTIPART_RELATED+"]");
			}
			if(contentType.getParameterList()==null || contentType.getParameterList().size()<=0){
				throw new UtilsException("ContentType non conforme a quanto definito nella specifica MTOM/XOP (non sono presenti parametri)");
			}
			
			// type
			String type = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE);
			if(type==null){
				throw new UtilsException("ContentType non conforme a quanto definito nella specifica MTOM/XOP (Parametro '"+
						HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE+"' non presente)");
			}
			if(HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML.equals(type.toLowerCase())==false){
				throw new UtilsException("ContentType.parameters."+HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE+" ["+type+
						"] differente da quello atteso per un messaggio MTOM/XOP ["+HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML+"]");
			}
			
			// startInfo
			String startInfo = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_START_INFO);
			if(startInfo==null){
				throw new UtilsException("ContentType non conforme a quanto definito nella specifica MTOM/XOP (Parametro '"+
						HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_START_INFO+"' non presente)");
			}
			// startInfo puo' contenere il ';'
			return readBaseTypeFromContentType(startInfo);
			
		}
		catch(UtilsException e){
			throw e;
		}
		catch(Exception e){
			throw new UtilsException(e.getMessage(),e);
		}
	}


	
	public static String readMultipartBoundaryFromContentType(String cType) throws UtilsException {
		try{
			ContentType contentType = new ContentType(cType);
			String boundaryParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_BOUNDARY); 
			if (boundaryParam != null) {
				return boundaryParam.trim();
			}
			return null;
		} catch (Exception e) {
			throw new UtilsException(e.getMessage(),e);
		}
	}
}