ContentTypeUtilities.java

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

  21. import java.util.ArrayList;
  22. import java.util.Iterator;
  23. import java.util.List;
  24. import java.util.Map;

  25. import javax.xml.soap.SOAPException;

  26. import org.openspcoop2.utils.Utilities;
  27. import org.openspcoop2.utils.UtilsException;
  28. import org.openspcoop2.utils.mime.MultipartUtils;
  29. import org.openspcoop2.utils.regexp.RegExpNotFoundException;
  30. import org.openspcoop2.utils.regexp.RegularExpressionEngine;
  31. import org.slf4j.Logger;

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



  36. /**
  37.  * ContentTypeUtilities
  38.  *
  39.  * @author Andrea Poli (apoli@link.it)
  40.  * @author $Author$
  41.  * @version $Rev$, $Date$
  42.  */
  43. public class ContentTypeUtilities {
  44.    
  45.    
  46.    
  47.     // Utilities generiche
  48.    
  49.     public static void validateContentType(String ct) throws UtilsException{
  50.         try {
  51.             if(ct!=null && !"".equals(ct)) {
  52.                
  53.                 (new javax.mail.internet.ContentType(ct)).getBaseType(); // uso javax.mail per validare, restituisce un errore migliore
  54.                
  55.                 if(ContentTypeUtilities.isMultipart(ct)){
  56.                     String internal = ContentTypeUtilities.getInternalMultipartContentType(ct);
  57.                     if(internal!=null){
  58.                         ContentTypeUtilities.isMtom(internal);
  59.                     }
  60.                 }
  61.             }
  62.         }catch(Throwable e) {
  63.             String msgError = e.getMessage();
  64.             if(msgError==null || "".equals(msgError) || "null".equals(msgError)) {
  65.                 msgError = Utilities.getInnerNotEmptyMessageException(e).getMessage();
  66.             }
  67.             if(msgError==null || "".equals(msgError) || "null".equals(msgError)) {
  68.                 msgError = "Parsing failed";
  69.             }
  70.             throw new UtilsException(msgError,e);
  71.         }
  72.     }
  73.    
  74.     public static String buildContentType(String baseType,Map<String, String> parameters) throws UtilsException{
  75.         try{
  76.             ContentType cType = new ContentType(baseType);
  77.             if(parameters!=null && parameters.size()>0){
  78.                 Iterator<String> itP = parameters.keySet().iterator();
  79.                 while (itP.hasNext()) {
  80.                     String parameterName = (String) itP.next();
  81.                     String parameterValue = parameters.get(parameterName);
  82.                     if(cType.getParameterList()==null){
  83.                         cType.setParameterList(new ParameterList());
  84.                     }
  85.                     cType.getParameterList().remove(parameterName);
  86.                     cType.getParameterList().set(parameterName, parameterValue);
  87.                 }
  88.                
  89.             }
  90.            
  91.             /*
  92.              * //import javax.mail.internet.ContentType;
  93.                 //import javax.mail.internet.ParameterList;

  94.                 import com.sun.xml.messaging.saaj.packaging.mime.internet.ContentType;
  95.                 import com.sun.xml.messaging.saaj.packaging.mime.internet.ParameterList;
  96.                
  97.                 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
  98.              *
  99.              * */
  100.            
  101.             String ct = cType.toString(); // il toString in presenza di action con valore http://... non funziona correttamente e genera valori action*0 e action*1
  102.            
  103.             // Reimplementare il toString non basta poiche' i ':' fanno schiantare un successivo parser del javax.mail.internet.ContentType
  104. //          StringBuilder ctBufferParam = new StringBuilder();
  105. //          ParameterList pList = cType.getParameterList();
  106. //          if(pList!=null && pList.size()>0) {
  107. //              java.util.Enumeration<String> en = pList.getNames();
  108. //              while (en.hasMoreElements()) {
  109. //                  String name = (String) en.nextElement();
  110. //                  ctBufferParam.append("; ");
  111. //                  ctBufferParam.append(name).append("=").append(pList.get(name));
  112. //              }
  113. //          }
  114. //          String ct = cType.getBaseType();
  115. //          if(ctBufferParam.length()>0) {
  116. //              ct = ct + ctBufferParam.toString();
  117. //          }
  118.             ct = normalizeToRfc7230(ct);            
  119.             ct = ct.trim();
  120.             return ct;
  121.         }catch(Exception e){
  122.             throw new RuntimeException("Error during buildContentType: "+e.getMessage(), e);
  123.         }
  124.     }
  125.    
  126.     public static String normalizeToRfc7230(String ct) {
  127.         // Line folding of headers has been deprecated in the latest HTTP RFC:
  128.         // http://tools.ietf.org/html/rfc7230#section-3.2.4
  129.         while (ct.contains("\r\n")) {
  130.             ct = ct.replace("\r\n", " ");
  131.         }
  132.         while (ct.contains("\r")) {
  133.             ct = ct.replace("\r", " ");
  134.         }
  135.         while (ct.contains("\n")) {
  136.             ct = ct.replace("\n", " ");
  137.         }
  138.         while (ct.contains("\t")) {
  139.             ct = ct.replace("\t", " ");
  140.         }
  141.         return ct.trim();
  142.     }
  143.    
  144.     public static String readBaseTypeFromContentType(String cType) throws UtilsException {
  145.         try{
  146.             ContentType contentType = new ContentType(cType);
  147.             return contentType.getBaseType();
  148.         } catch (Exception e) {
  149.             throw new UtilsException(e.getMessage(),e);
  150.         }
  151.     }
  152.     public static String readPrimaryTypeFromContentType(String cType) throws UtilsException {
  153.         try{
  154.             ContentType contentType = new ContentType(cType);
  155.             return contentType.getPrimaryType();
  156.         } catch (Exception e) {
  157.             throw new UtilsException(e.getMessage(),e);
  158.         }
  159.     }
  160.     public static String readSubTypeFromContentType(String cType) throws UtilsException {
  161.         try{
  162.             ContentType contentType = new ContentType(cType);
  163.             return contentType.getSubType();
  164.         } catch (Exception e) {
  165.             throw new UtilsException(e.getMessage(),e);
  166.         }
  167.     }
  168.    
  169.     public static String readCharsetFromContentType(String cType) throws UtilsException {
  170.         try{
  171.             ContentType contentType = new ContentType(cType);
  172.             String charsetParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_PARAMETER_CHARSET);
  173.             if (charsetParam != null) {
  174.                 return charsetParam.trim();
  175.             }
  176.             return null;
  177.         } catch (Exception e) {
  178.             throw new UtilsException(e.getMessage(),e);
  179.         }
  180.     }
  181.    
  182.    
  183.    
  184.     // match
  185.    
  186.     public static boolean isMatch(Logger logNullable, String contentTypeParam, String contentTypeAtteso) throws UtilsException {
  187.         List<String> l = new ArrayList<>();
  188.         l.add(contentTypeAtteso);
  189.         return isMatch(logNullable, contentTypeParam, l);
  190.     }
  191.     public static boolean isMatch(Logger logNullable, String contentTypeParam, List<String> contentTypeAttesi) throws UtilsException {
  192.        
  193.         if(contentTypeAttesi==null || contentTypeAttesi.isEmpty()) {
  194.             return true;
  195.         }
  196.        
  197.         String baseTypeHttp = contentTypeParam!=null ? ContentTypeUtilities.readBaseTypeFromContentType(contentTypeParam) : null;
  198.        
  199.         boolean found = false;
  200.         for (String checkContentType : contentTypeAttesi) {
  201.             if("empty".equals(checkContentType)){
  202.                 if(baseTypeHttp==null || "".equals(baseTypeHttp)) {
  203.                     found = true;
  204.                     break;
  205.                 }
  206.             }
  207.             else {
  208.                 if(baseTypeHttp==null) {
  209.                     continue;
  210.                 }
  211.                 if(checkContentType==null || "".equals(checkContentType) ||
  212.                         !checkContentType.contains("/") ||
  213.                         checkContentType.startsWith("/") ||
  214.                         checkContentType.endsWith("/")) {
  215.                     throw new UtilsException("Configurazione errata, content type indicato ("+checkContentType+") possiede un formato non corretto (atteso: type/subtype)");
  216.                 }
  217.                 String [] ctVerifica = checkContentType.split("/");
  218.                 String contentTypeEscaped = null;
  219.                 if(ctVerifica!=null && ctVerifica.length==2) {
  220.                     StringBuilder bf = new StringBuilder();
  221.                     String part1 = ctVerifica[0].trim();
  222.                     if("*".equals(part1)) {
  223.                         bf.append("(.+)");
  224.                     }
  225.                     else {
  226.                         // escape special char
  227.                         part1 = part1.replace("+", "\\+");
  228.                         bf.append(part1);
  229.                     }
  230.                     bf.append("/");
  231.                     String part2 = ctVerifica[1].trim();
  232.                     if("*".equals(part2)) {
  233.                         bf.append("(.+)");
  234.                     }
  235.                     else if(part2.startsWith("*")) {
  236.                         bf.append("(.+)");
  237.                         String sub = part2.substring(1);
  238.                         // escape special char
  239.                         sub = sub.replace("+", "\\+");
  240.                         bf.append(sub);
  241.                     }
  242.                     else {
  243.                         // escape special char
  244.                         part2 = part2.replace("+", "\\+");
  245.                         bf.append(part2);
  246.                     }
  247.                     contentTypeEscaped = bf.toString();
  248.                 }
  249.                 boolean isMatchEscaped = false; // gestisce le espressioni type/* e */*+xml
  250.                 boolean isMatchRegExp = false; // gestisce le espressioni regexpType/regexpSubType
  251.                 if(contentTypeEscaped!=null) {
  252.                     try {
  253.                         isMatchEscaped = RegularExpressionEngine.isMatch(baseTypeHttp, contentTypeEscaped);
  254.                     }catch(RegExpNotFoundException notFound) {
  255.                         // ignore
  256.                     }catch(Exception e) {
  257.                         throw new UtilsException(e.getMessage(),e);
  258.                     }
  259.                 }
  260.                 if(!isMatchEscaped) {
  261.                     try {
  262.                         isMatchRegExp = RegularExpressionEngine.isMatch(baseTypeHttp, checkContentType);
  263.                     }catch(RegExpNotFoundException notFound) {
  264.                         // ignore
  265.                     }catch(Exception e) {
  266.                         if(contentTypeEscaped==null) {
  267.                             throw new UtilsException(e.getMessage(),e);
  268.                         }
  269.                         else {
  270.                             // ignore
  271.                             // per evitare errori tipo:
  272.                             //org.openspcoop2.utils.UtilsException: Validazione del pattern indicato [*/*son] fallita: Dangling meta character '*' near index 0
  273.                             //                                                                        */*son
  274.                             //                                                                        ^
  275.                             if(logNullable!=null) {
  276.                                 logNullable.debug("isMatch failed: "+e.getMessage(),e);
  277.                             }
  278.                         }
  279.                     }
  280.                 }
  281.                 if(isMatchEscaped || isMatchRegExp) {
  282.                     found = true;
  283.                     break;
  284.                 }
  285.             }
  286.            
  287.         }
  288.         return found;
  289.     }
  290.    
  291.    
  292.    
  293.    
  294.     // Utilities Multipart
  295.    
  296.     public static boolean isMultipartType(String cType) throws UtilsException {
  297.         try{
  298.             ContentType contentType = new ContentType(cType);
  299.             String baseType = contentType.getBaseType();
  300.             if(baseType!=null) {
  301.                 baseType = baseType.toLowerCase();
  302.             }
  303.             if(baseType!=null && baseType.startsWith(HttpConstants.CONTENT_TYPE_MULTIPART_TYPE+"/")){
  304.                 return true;
  305.             }
  306.             return false;
  307.            
  308.         } catch (Exception e) {
  309.             throw new UtilsException(e.getMessage(),e);
  310.         }
  311.     }
  312.    
  313.     @Deprecated
  314.     public static boolean isMultipart(String cType) throws UtilsException {
  315.         return isMultipartRelated(cType);
  316.     }
  317.     public static boolean isMultipartRelated(String cType) throws UtilsException {
  318.         return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_RELATED);
  319.     }
  320.     public static boolean isMultipartMixed(String cType) throws UtilsException {
  321.         return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_MIXED);
  322.     }
  323.     public static boolean isMultipartAlternative(String cType) throws UtilsException {
  324.         return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_ALTERNATIVE);
  325.     }
  326.     public static boolean isMultipartFormData(String cType) throws UtilsException {
  327.         return _isMultipart(cType, HttpConstants.CONTENT_TYPE_MULTIPART_FORM_DATA);
  328.     }
  329.     private static boolean _isMultipart(String cType, String expected) throws UtilsException {
  330.         try{
  331.             ContentType contentType = new ContentType(cType);
  332.             String baseType = contentType.getBaseType();
  333.             if(baseType!=null) {
  334.                 baseType = baseType.toLowerCase();
  335.             }
  336.             if(baseType!=null && baseType.equals(expected)){
  337.                 return true;
  338.             }
  339.             return false;
  340.            
  341.         } catch (Exception e) {
  342.             throw new UtilsException(e.getMessage(),e);
  343.         }
  344.     }
  345.    
  346.     public static boolean isMultipartContentType(String cType) throws UtilsException {
  347.         try{
  348.             ContentType contentType = new ContentType(cType);
  349.             String baseType = contentType.getBaseType();
  350.             if(baseType!=null) {
  351.                 baseType = baseType.toLowerCase();
  352.             }
  353.             if(baseType!=null && baseType.startsWith( (HttpConstants.CONTENT_TYPE_MULTIPART_TYPE+"/")) ){
  354.                 return true;
  355.             }
  356.             return false;
  357.            
  358.         } catch (Exception e) {
  359.             throw new UtilsException(e.getMessage(),e);
  360.         }
  361.     }
  362.    
  363.     public static String getInternalMultipartContentType(String cType) throws UtilsException {
  364.         try{
  365.             ContentType contentType = new ContentType(cType);
  366.             String baseType = contentType.getBaseType();
  367.             if(baseType!=null) {
  368.                 baseType = baseType.toLowerCase();
  369.             }
  370.             String internalContentType = null;
  371.             boolean mtom = false;
  372.             if(baseType == null) {
  373.                 internalContentType = null;
  374.             }
  375.             else if(baseType.equals(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED)){
  376.                 String typeParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE);
  377.                 if (typeParam != null) {
  378.                     internalContentType = typeParam.toLowerCase();
  379.                     if(HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML.equals(internalContentType)){
  380.                         mtom = true;
  381.                     }
  382.                 }
  383.             }
  384.             else {
  385.                 internalContentType = baseType;
  386.             }
  387.            
  388.             if(mtom) {
  389.                 internalContentType = readInternalMultipartMtomContentType(contentType);
  390.             }
  391.            
  392.             return internalContentType;
  393.         } catch (Exception e) {
  394.             throw new UtilsException(e.getMessage(),e);
  395.         }
  396.     }
  397.    
  398.     @Deprecated
  399.     public static String buildMultipartContentType(byte [] message, String type) throws UtilsException{
  400.         return buildMultipartRelatedContentType(message, type);
  401.     }
  402.     public static String buildMultipartRelatedContentType(byte [] message, String type) throws UtilsException{
  403.         return buildMultipartContentType(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED_SUBTYPE, message, type);
  404.     }
  405.     public static String buildMultipartContentType(String subtype, byte [] message, String type) throws UtilsException{
  406.         if(MultipartUtils.messageWithAttachment(message)){
  407.             String IDfirst  = MultipartUtils.firstContentID(message);
  408.             return buildMultipartContentType(subtype, message, type, IDfirst);
  409.         }
  410.         throw new UtilsException("Messaggio non contiene una struttura mime");
  411.     }
  412.     @Deprecated
  413.     public static String buildMultipartContentType(byte [] message, String type, String ID) throws UtilsException{
  414.         return buildMultipartRelatedContentType(message, type, ID);
  415.     }
  416.     public static String buildMultipartRelatedContentType(byte [] message, String type, String ID) throws UtilsException{
  417.         return buildMultipartContentType(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED_SUBTYPE, message, type, ID);
  418.     }
  419.     public static String buildMultipartContentType(String subtype, byte [] message, String type, String ID) throws UtilsException{
  420.         if(MultipartUtils.messageWithAttachment(message)){
  421.                        
  422.             String boundary = MultipartUtils.findBoundary(message);
  423.             if(boundary==null){
  424.                 throw new UtilsException("Errore avvenuto durante la lettura del boundary associato al multipart message.");
  425.             }
  426.             StringBuilder bf = new StringBuilder();
  427.             bf.append(HttpConstants.CONTENT_TYPE_MULTIPART_TYPE+"/"+subtype);
  428.             if(type!=null){
  429.                 bf.append("; ").append(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE).append("=\"").append(type).append("\"");
  430.             }
  431.             if(ID!=null){
  432.                 bf.append("; ").append(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_START).append("=\"").append(ID).append("\"");
  433.             }
  434.             bf.append("; ").append(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_BOUNDARY).append("=\"").append(boundary.substring(2,boundary.length())).append("\"");
  435.            
  436.             return bf.toString();
  437.         }
  438.         throw new UtilsException("Messaggio non contiene una struttura mime");
  439.     }
  440.    
  441.    
  442.    
  443.    
  444.     // Utilities MTOM
  445.    
  446.     public static boolean isMtom(String cType) throws UtilsException{
  447.         try{
  448.             ContentType contentType = new ContentType(cType);
  449.             String baseType = contentType.getBaseType();
  450.             if(baseType!=null) {
  451.                 baseType = baseType.toLowerCase();
  452.             }
  453.             boolean mtom = false;
  454.             if(baseType!=null && baseType.equals(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED)){
  455.                 String typeParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE);
  456.                 if (typeParam == null) {
  457.                     throw new SOAPException("Missing '"+HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE+"' parameter in "+HttpConstants.CONTENT_TYPE_MULTIPART_RELATED);
  458.                 } else {
  459.                     String soapContentType = typeParam.toLowerCase();
  460.                     if(HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML.equals(soapContentType)){
  461.                         mtom = true;
  462.                     }
  463.                 }
  464.             }
  465.             return mtom;
  466.            
  467.         } catch (Exception e) {
  468.             throw new UtilsException(e.getMessage(),e);
  469.         }
  470.     }
  471.    
  472.     public static String readInternalMultipartMtomContentType(String contentType) throws UtilsException{
  473.         try{
  474.             return readInternalMultipartMtomContentType(new ContentType(contentType));
  475.         }
  476.         catch(UtilsException e){
  477.             throw e;
  478.         }
  479.         catch(Exception e){
  480.             throw new UtilsException(e.getMessage(),e);
  481.         }
  482.     }
  483.     public static String readInternalMultipartMtomContentType(ContentType contentType) throws UtilsException{
  484.         try{
  485.             if(contentType==null){
  486.                 throw new UtilsException("ContentType non fornito");
  487.             }
  488.            
  489.             // baseType
  490.             if(contentType.getBaseType()==null){
  491.                 throw new UtilsException("ContentType.baseType non definito");
  492.             }
  493.             if(HttpConstants.CONTENT_TYPE_MULTIPART_RELATED.equals(contentType.getBaseType().toLowerCase())==false){
  494.                 throw new UtilsException("ContentType.baseType ["+contentType.getBaseType()+
  495.                         "] differente da quello atteso per un messaggio MTOM/XOP ["+HttpConstants.CONTENT_TYPE_MULTIPART_RELATED+"]");
  496.             }
  497.             if(contentType.getParameterList()==null || contentType.getParameterList().size()<=0){
  498.                 throw new UtilsException("ContentType non conforme a quanto definito nella specifica MTOM/XOP (non sono presenti parametri)");
  499.             }
  500.            
  501.             // type
  502.             String type = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE);
  503.             if(type==null){
  504.                 throw new UtilsException("ContentType non conforme a quanto definito nella specifica MTOM/XOP (Parametro '"+
  505.                         HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE+"' non presente)");
  506.             }
  507.             if(HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML.equals(type.toLowerCase())==false){
  508.                 throw new UtilsException("ContentType.parameters."+HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_TYPE+" ["+type+
  509.                         "] differente da quello atteso per un messaggio MTOM/XOP ["+HttpConstants.CONTENT_TYPE_APPLICATION_XOP_XML+"]");
  510.             }
  511.            
  512.             // startInfo
  513.             String startInfo = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_START_INFO);
  514.             if(startInfo==null){
  515.                 throw new UtilsException("ContentType non conforme a quanto definito nella specifica MTOM/XOP (Parametro '"+
  516.                         HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_START_INFO+"' non presente)");
  517.             }
  518.             // startInfo puo' contenere il ';'
  519.             return readBaseTypeFromContentType(startInfo);
  520.            
  521.         }
  522.         catch(UtilsException e){
  523.             throw e;
  524.         }
  525.         catch(Exception e){
  526.             throw new UtilsException(e.getMessage(),e);
  527.         }
  528.     }


  529.    
  530.     public static String readMultipartBoundaryFromContentType(String cType) throws UtilsException {
  531.         try{
  532.             ContentType contentType = new ContentType(cType);
  533.             String boundaryParam = contentType.getParameter(HttpConstants.CONTENT_TYPE_MULTIPART_PARAMETER_BOUNDARY);
  534.             if (boundaryParam != null) {
  535.                 return boundaryParam.trim();
  536.             }
  537.             return null;
  538.         } catch (Exception e) {
  539.             throw new UtilsException(e.getMessage(),e);
  540.         }
  541.     }
  542. }