StringUtil.java

  1. /*
  2.  * Copyright (c) 2002-2022, City of Paris
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  *  1. Redistributions of source code must retain the above copyright notice
  10.  *     and the following disclaimer.
  11.  *
  12.  *  2. Redistributions in binary form must reproduce the above copyright notice
  13.  *     and the following disclaimer in the documentation and/or other materials
  14.  *     provided with the distribution.
  15.  *
  16.  *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
  17.  *     contributors may be used to endorse or promote products derived from
  18.  *     this software without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
  24.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30.  * POSSIBILITY OF SUCH DAMAGE.
  31.  *
  32.  * License 1.0
  33.  */
  34. package fr.paris.lutece.util.string;

  35. import fr.paris.lutece.portal.service.util.AppLogService;
  36. import fr.paris.lutece.portal.service.util.AppPropertiesService;

  37. import java.io.BufferedReader;
  38. import java.io.ByteArrayInputStream;
  39. import java.io.ByteArrayOutputStream;
  40. import java.io.IOException;
  41. import java.io.InputStreamReader;
  42. import java.text.Normalizer;
  43. import java.util.zip.GZIPInputStream;
  44. import java.util.zip.GZIPOutputStream;

  45. import org.apache.commons.lang3.StringUtils;

  46. /**
  47.  * This class provides String utils.
  48.  */
  49. public final class StringUtil
  50. {
  51.     private static final String PROPERTY_XSS_CHARACTERS = "input.xss.characters";
  52.     private static final String PROPERTY_MAIL_PATTERN = "mail.accepted.pattern";
  53.     private static final String STRING_CODE_PATTERN = "^[\\w]+$";
  54.     private static final String CONSTANT_AT = "@";
  55.     private static final String CONSTANT_UTF8 = "UTF-8";
  56.     private static final String EMAIL_PATTERN = "^[\\w_.\\-]+@[\\w_.\\-]+\\.[\\w]+$";
  57.    
  58.     // The characters that are considered dangerous for XSS attacks
  59.     private static char [ ] _aXssCharacters;
  60.     private static String _xssCharactersAsString;

  61.     /**
  62.      * Constructor with no parameter
  63.      */
  64.     private StringUtil( )
  65.     {
  66.     }

  67.     /**
  68.      * This function substitutes all occurences of a given bookmark by a given value
  69.      *
  70.      * @param strSource
  71.      *            The input string that contains bookmarks to replace
  72.      * @param strValue
  73.      *            The value to substitute to the bookmark
  74.      * @param strBookmark
  75.      *            The bookmark name
  76.      * @return The output string.
  77.      */
  78.     public static String substitute( String strSource, String strValue, String strBookmark )
  79.     {
  80.         StringBuilder strResult = new StringBuilder( );
  81.         int nPos = strSource.indexOf( strBookmark );
  82.         String strModifySource = strSource;

  83.         while ( nPos != -1 )
  84.         {
  85.             strResult.append( strModifySource.substring( 0, nPos ) );
  86.             strResult.append( strValue );
  87.             strModifySource = strModifySource.substring( nPos + strBookmark.length( ) );
  88.             nPos = strModifySource.indexOf( strBookmark );
  89.         }

  90.         strResult.append( strModifySource );

  91.         return strResult.toString( );
  92.     }

  93.     /**
  94.      * This function converts French diacritics characters into non diacritics.
  95.      *
  96.      * @param strSource
  97.      *            The String to convert
  98.      * @return The sTring converted to French non diacritics characters
  99.      */
  100.     public static String replaceAccent( String strSource )
  101.     {
  102.         String strNormalized = Normalizer.normalize( strSource, Normalizer.Form.NFKD );
  103.         strNormalized = strNormalized.replaceAll( "\\p{M}", "" );

  104.         return strNormalized;
  105.     }

  106.     /**
  107.      * Checks if a string literal contains any HTML special characters (&, ", ' <, >).
  108.      *
  109.      * @param strValue
  110.      *            The string literal to check
  111.      * @return True if the string literal contains any special character
  112.      */
  113.     public static boolean containsHtmlSpecialCharacters( String strValue )
  114.     {
  115.         return ( ( strValue.indexOf( '"' ) > -1 ) || ( strValue.indexOf( '&' ) > -1 ) || ( strValue.indexOf( '<' ) > -1 ) || ( strValue.indexOf( '>' ) > -1 ) );
  116.     }

  117.     /**
  118.      * Checks if a String contains characters that could be used for a cross-site scripting attack.
  119.      *
  120.      * @param strValue
  121.      *            a character String
  122.      * @return true if the String contains illegal characters
  123.      */
  124.     public static synchronized boolean containsXssCharacters( String strValue )
  125.     {
  126.         // Read XSS characters from properties file if not already initialized
  127.         if ( _aXssCharacters == null )
  128.         {
  129.             _aXssCharacters = AppPropertiesService.getProperty( PROPERTY_XSS_CHARACTERS ).toCharArray( );
  130.         }

  131.         return containsXssCharacters( strValue, _aXssCharacters );
  132.     }

  133.     /**
  134.      * Checks if a String contains characters that could be used for a cross-site scripting attack.
  135.      *
  136.      * @param strValue
  137.      *            a character String
  138.      * @param aXssCharacters
  139.      *            a Xss characters tab to check in strValue
  140.      * @return true if the String contains illegal characters
  141.      */
  142.     public static synchronized boolean containsXssCharacters( String strValue, char [ ] aXssCharacters )
  143.     {
  144.         // Read XSS characters from properties file if not already initialized
  145.         boolean bContains = false;

  146.         if ( aXssCharacters != null )
  147.         {
  148.             for ( int nIndex = 0; !bContains && ( nIndex < aXssCharacters.length ); nIndex++ )
  149.             {
  150.                 bContains = strValue.lastIndexOf( aXssCharacters [nIndex] ) >= 0;
  151.             }
  152.         }

  153.         return bContains;
  154.     }

  155.     /**
  156.      * Checks if a String contains characters that could be used for a cross-site scripting attack.
  157.      *
  158.      * @param strValue
  159.      *            a character String
  160.      * @param strXssCharacters
  161.      *            a String wich contain a list of Xss characters to check in strValue
  162.      * @return true if the String contains illegal characters
  163.      */
  164.     public static synchronized boolean containsXssCharacters( String strValue, String strXssCharacters )
  165.     {
  166.         // Read XSS characters from properties file if not already initialized
  167.         if ( strXssCharacters != null )
  168.         {
  169.             return containsXssCharacters( strValue, strXssCharacters.toCharArray( ) );
  170.         }

  171.         return false;
  172.     }

  173.     /**
  174.      * Simple convenience method to return the XSS characters as a string, to include it in error messages.
  175.      *
  176.      * @return a String containing a comma-separated list of the XSS characters
  177.      */
  178.     public static synchronized String getXssCharactersAsString( )
  179.     {
  180.         // Read XSS characters from properties file if not already initialized
  181.         if ( _aXssCharacters == null )
  182.         {
  183.             _aXssCharacters = AppPropertiesService.getProperty( PROPERTY_XSS_CHARACTERS ).toCharArray( );
  184.         }

  185.         if ( _xssCharactersAsString == null )
  186.         {
  187.             StringBuilder sbfCharList = new StringBuilder( );

  188.             int iIndex;

  189.             for ( iIndex = 0; iIndex < ( _aXssCharacters.length - 1 ); iIndex++ )
  190.             {
  191.                 sbfCharList.append( _aXssCharacters [iIndex] );
  192.                 sbfCharList.append( ", " );
  193.             }

  194.             // Append last character outside of the loop to avoid trailing comma
  195.             sbfCharList.append( _aXssCharacters [iIndex] );
  196.             _xssCharactersAsString = sbfCharList.toString( );
  197.         }

  198.         return _xssCharactersAsString;
  199.     }

  200.     /**
  201.      * This function checks if an email is in a valid format Returns true if the email is valid
  202.      *
  203.      * @param strEmail
  204.      *            The mail to check
  205.      * @return boolean true if strEmail is valid
  206.      */
  207.     public static synchronized boolean checkEmail( String strEmail )
  208.     {
  209.         return strEmail.matches( AppPropertiesService.getProperty( PROPERTY_MAIL_PATTERN, EMAIL_PATTERN ) );
  210.     }

  211.     /**
  212.      * This function checks if an email is in a valid format, and is not in a banned domain names list. Returns true if the email is valid
  213.      *
  214.      * @param strEmail
  215.      *            The mail to check
  216.      * @param strBannedDomainNames
  217.      *            The list of banned domain names. Domain names may start with a '@' or not.
  218.      * @return boolean true if strEmail is valid, false otherwise
  219.      */
  220.     public static synchronized boolean checkEmailAndDomainName( String strEmail, String [ ] strBannedDomainNames )
  221.     {
  222.         boolean bIsValid = strEmail.matches( AppPropertiesService.getProperty( PROPERTY_MAIL_PATTERN, EMAIL_PATTERN ) );

  223.         return bIsValid && checkEmailDomainName( strEmail, strBannedDomainNames );
  224.     }

  225.     /**
  226.      * Check if a domain name of an email address is not in a banned domain names list.
  227.      *
  228.      * @param strEmail
  229.      *            Email addresse to check
  230.      * @param strBannedDomainNames
  231.      *            List of banned domain names
  232.      * @return True if the email address is correct, false otherwise
  233.      */
  234.     public static synchronized boolean checkEmailDomainName( String strEmail, String [ ] strBannedDomainNames )
  235.     {
  236.         if ( ( strBannedDomainNames != null ) && ( strBannedDomainNames.length > 0 ) )
  237.         {
  238.             int nOffset;

  239.             if ( strBannedDomainNames [0].contains( CONSTANT_AT ) )
  240.             {
  241.                 nOffset = 0;
  242.             }
  243.             else
  244.             {
  245.                 nOffset = 1;
  246.             }

  247.             int nIndex = strEmail.indexOf( CONSTANT_AT );

  248.             if ( ( nIndex >= 0 ) && ( ( nIndex + nOffset ) < strEmail.length( ) ) )
  249.             {
  250.                 String strDomainName = strEmail.substring( nIndex + nOffset );

  251.                 for ( String strDomain : strBannedDomainNames )
  252.                 {
  253.                     if ( strDomainName.equals( strDomain ) )
  254.                     {
  255.                         return false;
  256.                     }
  257.                 }
  258.             }
  259.         }

  260.         return true;
  261.     }

  262.     /**
  263.      * Check a code key.<br>
  264.      * Return true if each character of String is
  265.      * <ul>
  266.      * <li>number</li>
  267.      * <li>lower case</li>
  268.      * <li>upper case</li>
  269.      * </ul>
  270.      *
  271.      * @param strCodeKey
  272.      *            The code Key
  273.      * @return True if code key is valid
  274.      */
  275.     public static synchronized boolean checkCodeKey( String strCodeKey )
  276.     {
  277.         return strCodeKey != null && strCodeKey.matches( STRING_CODE_PATTERN );
  278.     }

  279.     /**
  280.      * Converts <code>strValue</code> to an int value.
  281.      *
  282.      * @param strValue
  283.      *            the value to convert
  284.      * @param nDefaultValue
  285.      *            the default returned value
  286.      * @return <code>strValue</code> int value, <code>nDefaultValue</code> if strValue is not an Integer.
  287.      */
  288.     public static int getIntValue( String strValue, int nDefaultValue )
  289.     {
  290.         try
  291.         {
  292.             return Integer.parseInt( strValue );
  293.         }
  294.         catch( NumberFormatException nfe )
  295.         {
  296.             AppLogService.error( nfe.getMessage( ), nfe );
  297.         }

  298.         return nDefaultValue;
  299.     }

  300.     /**
  301.      * Return true if any of the strings is empty, false otherwise
  302.      *
  303.      * @param strings
  304.      *            the strings to test
  305.      * @return
  306.      */
  307.     public static boolean isAnyEmpty( String... strings )
  308.     {
  309.         for ( String string : strings )
  310.         {
  311.             if ( StringUtils.isEmpty( string ) )
  312.             {
  313.                 return true;
  314.             }
  315.         }
  316.         return false;
  317.     }

  318.     /**
  319.      * compress (with default UTF-8 encoding)
  320.      *
  321.      * @param the string to compress
  322.      * @return the compressed string
  323.      * @throws IOException
  324.      */
  325.     public static byte[] compress(String str) throws IOException {

  326.         if (str == null || str.length() == 0) {
  327.             return "".getBytes( CONSTANT_UTF8 );
  328.         }

  329.         ByteArrayOutputStream out = new ByteArrayOutputStream();
  330.         GZIPOutputStream gzip = new GZIPOutputStream(out);
  331.         gzip.write( str.getBytes( CONSTANT_UTF8 ) );
  332.         gzip.close( );

  333.         return out.toByteArray();
  334.     }

  335.     /**
  336.      * uncompress (with default UTF-8 encoding)
  337.      *
  338.      * @param the compressed string
  339.      * @return the uncompressed string
  340.      * @throws IOException
  341.      */
  342.     public static String decompress(byte[] bytes) throws IOException {
  343.         return decompress( bytes, CONSTANT_UTF8);
  344.     }

  345.     /**
  346.      * uncompress
  347.      *
  348.      * @param the compressed string
  349.      * @param the encoding
  350.      * @return the uncompressed string
  351.      * @throws IOException
  352.      */
  353.     public static String decompress(byte[] bytes, String encoding) throws IOException {

  354.         if (bytes == null || bytes.length == 0) {
  355.             return "";
  356.         }

  357.         GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes));

  358.         ByteArrayOutputStream out = new ByteArrayOutputStream();

  359.         byte[] b = new byte[4096];
  360.         int len;
  361.         while ( (len = gis.read( b ) ) >= 0 )
  362.         {
  363.             out.write(b, 0, len);
  364.         }

  365.         return out.toString(encoding);
  366.     }
  367. }