View Javadoc
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 }