1 /*
2 * Copyright (c) 2002-2014, Mairie de 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
36 import fr.paris.lutece.portal.service.util.AppLogService;
37 import fr.paris.lutece.portal.service.util.AppPropertiesService;
38
39 import java.text.Normalizer;
40
41
42 /**
43 * This class provides String utils.
44 */
45 public final class StringUtil
46 {
47 private static final String PROPERTY_XSS_CHARACTERS = "input.xss.characters";
48 private static final String EMAIL_PATTERN = "^[\\w_.\\-]+@[\\w_.\\-]+\\.[\\w]+$";
49 private static final String STRING_CODE_PATTERN = "^[\\w]+$";
50 private static final String CONSTANT_AT = "@";
51
52 // The characters that are considered dangerous for XSS attacks
53 private static char[] _aXssCharacters;
54 private static String _xssCharactersAsString;
55
56 /**
57 * Constructor with no parameter
58 */
59 private StringUtil( )
60 {
61 }
62
63 /**
64 * This function substitutes all occurences of a given bookmark by a given value
65 *
66 * @param strSource The input string that contains bookmarks to replace
67 * @param strValue The value to substitute to the bookmark
68 * @param strBookmark The bookmark name
69 * @return The output string.
70 */
71 public static String substitute( String strSource, String strValue, String strBookmark )
72 {
73 StringBuilder strResult = new StringBuilder( );
74 int nPos = strSource.indexOf( strBookmark );
75 String strModifySource = strSource;
76
77 while ( nPos != -1 )
78 {
79 strResult.append( strModifySource.substring( 0, nPos ) );
80 strResult.append( strValue );
81 strModifySource = strModifySource.substring( nPos + strBookmark.length( ) );
82 nPos = strModifySource.indexOf( strBookmark );
83 }
84
85 strResult.append( strModifySource );
86
87 return strResult.toString( );
88 }
89
90 /**
91 * This function converts French diacritics characters into non diacritics.
92 *
93 * @param strSource The String to convert
94 * @return The sTring converted to French non diacritics characters
95 */
96 public static String replaceAccent( String strSource )
97 {
98 String strNormalized = Normalizer.normalize( strSource, Normalizer.Form.NFKD );
99 strNormalized = strNormalized.replaceAll( "\\p{M}", "" );
100
101 return strNormalized;
102 }
103
104 /**
105 * Checks if a string literal contains any HTML special characters
106 * (&, ", ' <, >).
107 * @param strValue The string literal to check
108 * @return True if the string literal contains any special character
109 */
110 public static boolean containsHtmlSpecialCharacters( String strValue )
111 {
112 return ( ( strValue.indexOf( '"' ) > -1 ) || ( strValue.indexOf( '&' ) > -1 ) ||
113 ( strValue.indexOf( '<' ) > -1 ) || ( strValue.indexOf( '>' ) > -1 ) );
114 }
115
116 /**
117 * Checks if a String contains characters that could be used for a
118 * cross-site scripting attack.
119 *
120 * @param strValue a character String
121 * @return true if the String contains illegal characters
122 */
123 public static synchronized boolean containsXssCharacters( String strValue )
124 {
125 // Read XSS characters from properties file if not already initialized
126 if ( _aXssCharacters == null )
127 {
128 _aXssCharacters = AppPropertiesService.getProperty( PROPERTY_XSS_CHARACTERS ).toCharArray( );
129 }
130
131 return containsXssCharacters( strValue, _aXssCharacters );
132 }
133
134 /**
135 * Checks if a String contains characters that could be used for a
136 * cross-site scripting attack.
137 *
138 * @param strValue a character String
139 * @param aXssCharacters 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
147 if ( aXssCharacters != null )
148 {
149 for ( int nIndex = 0; !bContains && ( nIndex < aXssCharacters.length ); nIndex++ )
150 {
151 bContains = strValue.lastIndexOf( aXssCharacters[nIndex] ) >= 0;
152 }
153 }
154
155 return bContains;
156 }
157
158 /**
159 * Checks if a String contains characters that could be used for a
160 * cross-site scripting attack.
161 *
162 * @param strValue a character String
163 * @param strXssCharacters a String wich contain a list of Xss characters to check in strValue
164 * @return true if the String contains illegal characters
165 */
166 public static synchronized boolean containsXssCharacters( String strValue, String strXssCharacters )
167 {
168 // Read XSS characters from properties file if not already initialized
169 if ( strXssCharacters != null )
170 {
171 return containsXssCharacters( strValue, strXssCharacters.toCharArray( ) );
172 }
173
174 return false;
175 }
176
177 /**
178 * Simple convenience method to return the XSS characters as a string, to
179 * include it in error messages.
180 *
181 * @return a String containing a comma-separated list of the XSS characters
182 */
183 public static synchronized String getXssCharactersAsString( )
184 {
185 // Read XSS characters from properties file if not already initialized
186 if ( _aXssCharacters == null )
187 {
188 _aXssCharacters = AppPropertiesService.getProperty( PROPERTY_XSS_CHARACTERS ).toCharArray( );
189 }
190
191 if ( _xssCharactersAsString == null )
192 {
193 StringBuilder sbfCharList = new StringBuilder( );
194
195 int iIndex;
196
197 for ( iIndex = 0; iIndex < ( _aXssCharacters.length - 1 ); iIndex++ )
198 {
199 sbfCharList.append( _aXssCharacters[iIndex] );
200 sbfCharList.append( ", " );
201 }
202
203 // Append last character outside of the loop to avoid trailing comma
204 sbfCharList.append( _aXssCharacters[iIndex] );
205 _xssCharactersAsString = sbfCharList.toString( );
206 }
207
208 return _xssCharactersAsString;
209 }
210
211 /**
212 * This function checks if an email is in a valid format Returns true if the
213 * email is valid
214 *
215 * @param strEmail The mail to check
216 * @return boolean true if strEmail is valid
217 */
218 public static synchronized boolean checkEmail( String strEmail )
219 {
220 return strEmail.matches( EMAIL_PATTERN );
221 }
222
223 /**
224 * 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
225 *
226 * @param strEmail The mail to check
227 * @param strBannedDomainNames The list of banned domain names. Domain names may start with a '@' or not.
228 * @return boolean true if strEmail is valid, false otherwise
229 */
230 public static synchronized boolean checkEmailAndDomainName( String strEmail, String[] strBannedDomainNames )
231 {
232 boolean bIsValid = strEmail.matches( EMAIL_PATTERN );
233
234 return bIsValid && checkEmailDomainName( strEmail, strBannedDomainNames );
235 }
236
237 /**
238 * Check if a domain name of an email address is not in a banned domain names list.
239 * @param strEmail Email addresse to check
240 * @param strBannedDomainNames List of banned domain names
241 * @return True if the email address is correct, false otherwise
242 */
243 public static synchronized boolean checkEmailDomainName( String strEmail, String[] strBannedDomainNames )
244 {
245 if ( ( strBannedDomainNames != null ) && ( strBannedDomainNames.length > 0 ) )
246 {
247 int nOffset;
248
249 if ( strBannedDomainNames[0].contains( CONSTANT_AT ) )
250 {
251 nOffset = 0;
252 }
253 else
254 {
255 nOffset = 1;
256 }
257
258 int nIndex = strEmail.indexOf( CONSTANT_AT );
259
260 if ( ( nIndex >= 0 ) && ( ( nIndex + nOffset ) < strEmail.length( ) ) )
261 {
262 String strDomainName = strEmail.substring( nIndex + nOffset );
263
264 for ( String strDomain : strBannedDomainNames )
265 {
266 if ( strDomainName.equals( strDomain ) )
267 {
268 return false;
269 }
270 }
271 }
272 }
273
274 return true;
275 }
276
277 /**
278 * Check a code key.<br />
279 * Return true if each character of String is
280 * <ul>
281 * <li>number</li>
282 * <li>lower case</li>
283 * <li>upper case</li>
284 * </ul>
285 *
286 * @param strCodeKey The code Key
287 * @return True if code key is valid
288 */
289 public static synchronized boolean checkCodeKey( String strCodeKey )
290 {
291 return ( strCodeKey == null ) ? false : strCodeKey.matches( STRING_CODE_PATTERN );
292 }
293
294 /**
295 * Converts <code>strValue</code> to an int value.
296 * @param strValue the value to convert
297 * @param nDefaultValue the default returned value
298 * @return <code>strValue</code> int value, <code>nDefaultValue</code> if strValue is not an Integer.
299 */
300 public static int getIntValue( String strValue, int nDefaultValue )
301 {
302 try
303 {
304 return Integer.parseInt( strValue );
305 }
306 catch ( NumberFormatException nfe )
307 {
308 AppLogService.error( nfe.getMessage( ), nfe );
309 }
310
311 return nDefaultValue;
312 }
313 }