StringUtil.java
/*
* Copyright (c) 2002-2022, City of Paris
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright notice
* and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice
* and the following disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* License 1.0
*/
package fr.paris.lutece.util.string;
import fr.paris.lutece.portal.service.util.AppLogService;
import fr.paris.lutece.portal.service.util.AppPropertiesService;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.Normalizer;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.lang3.StringUtils;
/**
* This class provides String utils.
*/
public final class StringUtil
{
private static final String PROPERTY_XSS_CHARACTERS = "input.xss.characters";
private static final String PROPERTY_MAIL_PATTERN = "mail.accepted.pattern";
private static final String STRING_CODE_PATTERN = "^[\\w]+$";
private static final String CONSTANT_AT = "@";
private static final String CONSTANT_UTF8 = "UTF-8";
private static final String EMAIL_PATTERN = "^[\\w_.\\-]+@[\\w_.\\-]+\\.[\\w]+$";
// The characters that are considered dangerous for XSS attacks
private static char [ ] _aXssCharacters;
private static String _xssCharactersAsString;
/**
* Constructor with no parameter
*/
private StringUtil( )
{
}
/**
* This function substitutes all occurences of a given bookmark by a given value
*
* @param strSource
* The input string that contains bookmarks to replace
* @param strValue
* The value to substitute to the bookmark
* @param strBookmark
* The bookmark name
* @return The output string.
*/
public static String substitute( String strSource, String strValue, String strBookmark )
{
StringBuilder strResult = new StringBuilder( );
int nPos = strSource.indexOf( strBookmark );
String strModifySource = strSource;
while ( nPos != -1 )
{
strResult.append( strModifySource.substring( 0, nPos ) );
strResult.append( strValue );
strModifySource = strModifySource.substring( nPos + strBookmark.length( ) );
nPos = strModifySource.indexOf( strBookmark );
}
strResult.append( strModifySource );
return strResult.toString( );
}
/**
* This function converts French diacritics characters into non diacritics.
*
* @param strSource
* The String to convert
* @return The sTring converted to French non diacritics characters
*/
public static String replaceAccent( String strSource )
{
String strNormalized = Normalizer.normalize( strSource, Normalizer.Form.NFKD );
strNormalized = strNormalized.replaceAll( "\\p{M}", "" );
return strNormalized;
}
/**
* Checks if a string literal contains any HTML special characters (&, ", ' <, >).
*
* @param strValue
* The string literal to check
* @return True if the string literal contains any special character
*/
public static boolean containsHtmlSpecialCharacters( String strValue )
{
return ( ( strValue.indexOf( '"' ) > -1 ) || ( strValue.indexOf( '&' ) > -1 ) || ( strValue.indexOf( '<' ) > -1 ) || ( strValue.indexOf( '>' ) > -1 ) );
}
/**
* Checks if a String contains characters that could be used for a cross-site scripting attack.
*
* @param strValue
* a character String
* @return true if the String contains illegal characters
*/
public static synchronized boolean containsXssCharacters( String strValue )
{
// Read XSS characters from properties file if not already initialized
if ( _aXssCharacters == null )
{
_aXssCharacters = AppPropertiesService.getProperty( PROPERTY_XSS_CHARACTERS ).toCharArray( );
}
return containsXssCharacters( strValue, _aXssCharacters );
}
/**
* Checks if a String contains characters that could be used for a cross-site scripting attack.
*
* @param strValue
* a character String
* @param aXssCharacters
* a Xss characters tab to check in strValue
* @return true if the String contains illegal characters
*/
public static synchronized boolean containsXssCharacters( String strValue, char [ ] aXssCharacters )
{
// Read XSS characters from properties file if not already initialized
boolean bContains = false;
if ( aXssCharacters != null )
{
for ( int nIndex = 0; !bContains && ( nIndex < aXssCharacters.length ); nIndex++ )
{
bContains = strValue.lastIndexOf( aXssCharacters [nIndex] ) >= 0;
}
}
return bContains;
}
/**
* Checks if a String contains characters that could be used for a cross-site scripting attack.
*
* @param strValue
* a character String
* @param strXssCharacters
* a String wich contain a list of Xss characters to check in strValue
* @return true if the String contains illegal characters
*/
public static synchronized boolean containsXssCharacters( String strValue, String strXssCharacters )
{
// Read XSS characters from properties file if not already initialized
if ( strXssCharacters != null )
{
return containsXssCharacters( strValue, strXssCharacters.toCharArray( ) );
}
return false;
}
/**
* Simple convenience method to return the XSS characters as a string, to include it in error messages.
*
* @return a String containing a comma-separated list of the XSS characters
*/
public static synchronized String getXssCharactersAsString( )
{
// Read XSS characters from properties file if not already initialized
if ( _aXssCharacters == null )
{
_aXssCharacters = AppPropertiesService.getProperty( PROPERTY_XSS_CHARACTERS ).toCharArray( );
}
if ( _xssCharactersAsString == null )
{
StringBuilder sbfCharList = new StringBuilder( );
int iIndex;
for ( iIndex = 0; iIndex < ( _aXssCharacters.length - 1 ); iIndex++ )
{
sbfCharList.append( _aXssCharacters [iIndex] );
sbfCharList.append( ", " );
}
// Append last character outside of the loop to avoid trailing comma
sbfCharList.append( _aXssCharacters [iIndex] );
_xssCharactersAsString = sbfCharList.toString( );
}
return _xssCharactersAsString;
}
/**
* This function checks if an email is in a valid format Returns true if the email is valid
*
* @param strEmail
* The mail to check
* @return boolean true if strEmail is valid
*/
public static synchronized boolean checkEmail( String strEmail )
{
return strEmail.matches( AppPropertiesService.getProperty( PROPERTY_MAIL_PATTERN, EMAIL_PATTERN ) );
}
/**
* 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
*
* @param strEmail
* The mail to check
* @param strBannedDomainNames
* The list of banned domain names. Domain names may start with a '@' or not.
* @return boolean true if strEmail is valid, false otherwise
*/
public static synchronized boolean checkEmailAndDomainName( String strEmail, String [ ] strBannedDomainNames )
{
boolean bIsValid = strEmail.matches( AppPropertiesService.getProperty( PROPERTY_MAIL_PATTERN, EMAIL_PATTERN ) );
return bIsValid && checkEmailDomainName( strEmail, strBannedDomainNames );
}
/**
* Check if a domain name of an email address is not in a banned domain names list.
*
* @param strEmail
* Email addresse to check
* @param strBannedDomainNames
* List of banned domain names
* @return True if the email address is correct, false otherwise
*/
public static synchronized boolean checkEmailDomainName( String strEmail, String [ ] strBannedDomainNames )
{
if ( ( strBannedDomainNames != null ) && ( strBannedDomainNames.length > 0 ) )
{
int nOffset;
if ( strBannedDomainNames [0].contains( CONSTANT_AT ) )
{
nOffset = 0;
}
else
{
nOffset = 1;
}
int nIndex = strEmail.indexOf( CONSTANT_AT );
if ( ( nIndex >= 0 ) && ( ( nIndex + nOffset ) < strEmail.length( ) ) )
{
String strDomainName = strEmail.substring( nIndex + nOffset );
for ( String strDomain : strBannedDomainNames )
{
if ( strDomainName.equals( strDomain ) )
{
return false;
}
}
}
}
return true;
}
/**
* Check a code key.<br>
* Return true if each character of String is
* <ul>
* <li>number</li>
* <li>lower case</li>
* <li>upper case</li>
* </ul>
*
* @param strCodeKey
* The code Key
* @return True if code key is valid
*/
public static synchronized boolean checkCodeKey( String strCodeKey )
{
return strCodeKey != null && strCodeKey.matches( STRING_CODE_PATTERN );
}
/**
* Converts <code>strValue</code> to an int value.
*
* @param strValue
* the value to convert
* @param nDefaultValue
* the default returned value
* @return <code>strValue</code> int value, <code>nDefaultValue</code> if strValue is not an Integer.
*/
public static int getIntValue( String strValue, int nDefaultValue )
{
try
{
return Integer.parseInt( strValue );
}
catch( NumberFormatException nfe )
{
AppLogService.error( nfe.getMessage( ), nfe );
}
return nDefaultValue;
}
/**
* Return true if any of the strings is empty, false otherwise
*
* @param strings
* the strings to test
* @return
*/
public static boolean isAnyEmpty( String... strings )
{
for ( String string : strings )
{
if ( StringUtils.isEmpty( string ) )
{
return true;
}
}
return false;
}
/**
* compress (with default UTF-8 encoding)
*
* @param the string to compress
* @return the compressed string
* @throws IOException
*/
public static byte[] compress(String str) throws IOException {
if (str == null || str.length() == 0) {
return "".getBytes( CONSTANT_UTF8 );
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(out);
gzip.write( str.getBytes( CONSTANT_UTF8 ) );
gzip.close( );
return out.toByteArray();
}
/**
* uncompress (with default UTF-8 encoding)
*
* @param the compressed string
* @return the uncompressed string
* @throws IOException
*/
public static String decompress(byte[] bytes) throws IOException {
return decompress( bytes, CONSTANT_UTF8);
}
/**
* uncompress
*
* @param the compressed string
* @param the encoding
* @return the uncompressed string
* @throws IOException
*/
public static String decompress(byte[] bytes, String encoding) throws IOException {
if (bytes == null || bytes.length == 0) {
return "";
}
GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(bytes));
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] b = new byte[4096];
int len;
while ( (len = gis.read( b ) ) >= 0 )
{
out.write(b, 0, len);
}
return out.toString(encoding);
}
}