Validación de UTF-8 en Java

Una de las peores pesadillas de cualquier programador es la multitud de Encodings existentes.

Dependiendo del país y/o lengua, la representación binaria de un mismo carácter (por ejemplo, la Ñ, o una vocal con acento) varía. Esto hace que una aplicación muestre problemas si no gestiona correctamente los posibles encodings que pueda encontrarse al pasar a un entorno de producción.

Para poder evitar este tipo de problemas, es importante ser capaz de detectar cuando una cadena de texto no sigue el Encoding usado por la aplicación.

Es por esto que nuestro equipo de ingenieros ha creado este código que detecta Strings de Java que su contenido no sea UTF-8.



import java.io.BufferedReader; import java.io.FileReader; public class UTF8Utils { /** * From Wikipedia: * Not all sequences of bytes are valid UTF-8. A UTF-8 decoder should be prepared for: * * the red invalid bytes in the above table * * an unexpected continuation byte -> Checked. * * a start byte not followed by enough continuation bytes -> Checked. * * an Overlong Encoding as described above -> Not checked by this algorithm. * * A 4-byte sequence (starting with 0xF4) that decodes to a value greater than U+10FFFF -> Not checked by this algorithm. * @param text * @return true if text is a UTF8 correct string. */ public static boolean isUTF8Correct(String text) { try { byte[] utf8 = text.getBytes("UTF-8"); for ( int i = 0 ; i < utf8.length ; i++ ) { // Unexpected continuation byte. if ( isUTF8Continuation(utf8[i])) { return false; } // Leading ones -> More than one byte involved with its continuations. int leadingOne = leadingOnes(utf8[i]); for ( int j = i + 1 ; j <= i + leadingOne ; j++ ) { if ( ! isUTF8Continuation(utf8[j])) { return false; } } i += leadingOne; } return true; } catch (Exception e) { return false; } } /** * Calculates leading ones in a byte. In utf8, the leading ones mark the length in bytes of the character. * @param code * @return */ private static int leadingOnes(byte code) { int leading = 0; while ( (code & 0x80) != 0 ) { leading ++; code = (byte) (code << 2); } return leading; } /** * Checks that the given byte is a UTF8 continuation byte. * @param code * @return */ private static boolean isUTF8Continuation(byte code) { // If the byte starts with 10xxxxxx this is a one byte length start. return ( ( code & 0x80 ) == 0x80 ) && ( ( ~code & 0x40 ) == 0x40 ); } }