Escenario de uso

Hemos implementado un webservice que nuestra interfaz web de los desarrolladores de uso (a través de una api php) internamente para mostrar los datos del producto. En el sitio web el usuario introduce algo (es decir, una cadena de consulta). Internamente el sitio web hace una llamada al servicio a través de la api.

Nota: usamos restlet, no tomcat

Problema Original

Firefox 3.0.10 parece referirse a la codificación seleccionada en el navegador y codificar una url de acuerdo a la codificación seleccionada. Este es el resultado en diferentes cadenas de consulta para ISO-8859-1 y UTF-8.

Nuestro sitio web reenvía la entrada del usuario y no convertirlo (que debería), por lo que se puede hacer una llamada al servicio a través de la api de llamar a un webservice usando una cadena de consulta que contiene diéresis alemanas.

I. e. para una parte de la consulta que busca como

    ...v=abcädef

si «ISO-8859-1» está seleccionado, el enviado de parte de la consulta parece

...v=abc%E4def

pero si «UTF-8» está seleccionado, el enviado de parte de la consulta parece

...v=abc%C3%A4def

Solución Deseada

Como podemos controlar el servicio, debido a que hemos implementado, queremos comprobar en lado del servidor si la llamada no contiene caracteres utf-8, si es así, responder con un 4xx de estado http

Actual En Detalle La Solución De

De verificación para cada personaje ( == cadena.substring(i,i+1) )

  1. si el carácter.getBytes()[0] es igual a 63 por ‘?’
  2. si el Carácter.getType(de carácter.charAt(0)) devuelve OTHER_SYMBOL

Código

protected List< String > getNonUnicodeCharacters( String s ) {
  final List< String > result = new ArrayList< String >();
  for ( int i = 0 , n = s.length() ; i < n ; i++ ) {
    final String character = s.substring( i , i + 1 );
    final boolean isOtherSymbol = 
      ( int ) Character.OTHER_SYMBOL
       == Character.getType( character.charAt( 0 ) );
    final boolean isNonUnicode = isOtherSymbol 
      && character.getBytes()[ 0 ] == ( byte ) 63;
    if ( isNonUnicode )
      result.add( character );
  }
  return result;
}

Pregunta

Se trata de captura de todos válidos (no codificados en utf) de personajes?
¿Alguno de ustedes tiene una mejor (y más fácil) solución?

Nota: he comprobado URLDecoder con el siguiente código

final String[] test = new String[]{
  "v=abc%E4def",
  "v=abc%C3%A4def"
};
for ( int i = 0 , n = test.length ; i < n ; i++ ) {
    System.out.println( java.net.URLDecoder.decode(test[i],"UTF-8") );
    System.out.println( java.net.URLDecoder.decode(test[i],"ISO-8859-1") );
}

Esto imprime:

v=abc?def
v=abcädef
v=abcädef
v=abcädef

y no no lanzar un IllegalArgumentException suspiro

10 Comentarios

  1. 32

    Me hizo la misma pregunta,

    El manejo de la Codificación de Caracteres en URI en Tomcat

    Recientemente he encontrado una solución y funciona bastante bien para mí. Puede que desee darle una oportunidad. Aquí es lo que usted necesita hacer,

    1. Salir de su URI de la codificación Latin-1. En Tomcat, agregar URIEncoding=»ISO-8859-1″ del Conector en server.xml.
    2. Si usted tiene manualmente la dirección URL de decodificar, uso Latin1 como charset también.
    3. Utilizar el fixEncoding() función para corregir las codificaciones.

    Por ejemplo, para obtener un parámetro de cadena de consulta,

      String name = fixEncoding(request.getParameter("name"));

    Usted puede hacer esto siempre. Cadena con codificación correcta no es cambiado.

    El código se adjunta. Buena suerte!

     public static String fixEncoding(String latin1) {
      try {
       byte[] bytes = latin1.getBytes("ISO-8859-1");
       if (!validUTF8(bytes))
        return latin1;   
       return new String(bytes, "UTF-8");  
      } catch (UnsupportedEncodingException e) {
       //Impossible, throw unchecked
       throw new IllegalStateException("No Latin1 or UTF-8: " + e.getMessage());
      }
    
     }
    
     public static boolean validUTF8(byte[] input) {
      int i = 0;
      //Check for BOM
      if (input.length >= 3 && (input[0] & 0xFF) == 0xEF
        && (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) {
       i = 3;
      }
    
      int end;
      for (int j = input.length; i < j; ++i) {
       int octet = input[i];
       if ((octet & 0x80) == 0) {
        continue; //ASCII
       }
    
       //Check for UTF-8 leading byte
       if ((octet & 0xE0) == 0xC0) {
        end = i + 1;
       } else if ((octet & 0xF0) == 0xE0) {
        end = i + 2;
       } else if ((octet & 0xF8) == 0xF0) {
        end = i + 3;
       } else {
        //Java only supports BMP so 3 is max
        return false;
       }
    
       while (i < end) {
        i++;
        octet = input[i];
        if ((octet & 0xC0) != 0x80) {
         //Not a valid trailing byte
         return false;
        }
       }
      }
      return true;
     }

    EDITAR: Su enfoque no funciona por varias razones. Cuando hay errores de codificación, usted no puede confiar en que lo que está recibiendo de Tomcat. A veces se obtiene � o ?. Otras veces, no se consigue nada, getParameter() devuelve null. A decir que usted puede comprobar «?», lo que sucede a su cadena de consulta contiene válida «?» ?

    Además, no se debe rechazar cualquier solicitud. Este no es su culpa del usuario. Como mencioné en mi pregunta original, el navegador puede codificar la URL en UTF-8 o Latin-1. El usuario no tiene ningún control. Es necesario aceptar ambas. Cambiar tu servlet a Latin-1 conservará todos los personajes, incluso si están mal, para darnos una oportunidad de arreglarlo o tirarlo a la basura.

    La solución que he publicado aquí no es perfecto pero es el mejor que hemos encontrado hasta ahora.

    • Interesante! Pero tengo que objetar a tu comentario «solo Java soporta BMP». Los cuatro bytes límite en UTF-8 secuencias de bytes fue impuesta por el Consorcio Unicode, y es suficiente para manejar la gama completa de caracteres (U+0000..U+10FFFF), no sólo de la BMP.
    • El comentario correcto, probablemente, debe ser «nosotros sólo Nos preocupamos de BMP». Mi impresión fue que par suplente no funciona bien en Java.
    • Bueno, pregunté en Mayo 😉 de todos Modos, lo que hace el código anterior ¿? Hace convertir iso a utf-8? Yo no quiero convertir el código, simplemente comprobar si la codificación es la derecha y producir un error si no lo es. Por favor, ver a mi de la solución anterior de nuevo y comprobar si es correcto, ¿de acuerdo?
    • La solución no es ir a trabajar. Si es incorrecto se utiliza la codificación, obtendrá signos de interrogación, en lugar de la excepción. Sólo uso mi función validUTF8(). Si es verdad, lo MÁS PROBABLE es UTF8. De lo contrario, es Latin-1. Usted tiene que utilizar la codificación Latin-1 en todas partes en el servidor para comprobar este trabajo.
    • Sí, como he dicho, en : 1. comprobar si el carácter.getBytes()[0] es igual a 63 por ‘?’, 2. comprobar si el Carácter.getType(de carácter.charAt(0)) devuelve OTHER_SYMBOL. Y este no de trabajo para mí. Si usted puede demostrar lo contrario, por favor, hágamelo saber…
    • Véase mi edición ……………..
    • Coder: el código detecta correctamente las cuatro bytes UTF-8 secuencias, que es el máximo permitido por la especificación Unicode, por lo que el comentario en realidad no tiene sentido. Cuando el texto se convierte en Java las cadenas, los de cuatro secuencias de bytes se convertirá en pares suplentes, Java que maneja correctamente-no sólo de forma transparente.
    • Coder: En primer lugar gracias por tu tiempo. Parece que ha habido algún malentendido a causa de mi inprecise pregunta, que he tratado de aclarar. Por favor vea mis ediciones. En segundo lugar: no estoy de acuerdo con su «no hay que rechazar cualquier…» de la propuesta, porque estamos en el nivel de interfaz. Tengo para asegurarse de que el usuario del servicio siempre se utiliza la codificación correcta. Si mi solución es incorrecta, ¿de qué otra manera puedo lograr eso?
    • Coder: ¿Podría usted por favor, añadir algunos comentarios a tu código para que me ayude a entender lo que está haciendo?
    • validUTF8 no trabajo aquí.

  2. 14

    Puede utilizar un CharsetDecoder configurado para lanzar una excepción si los caracteres no válidos se encuentran:

     CharsetDecoder UTF8Decoder =
          Charset.forName("UTF8").newDecoder().onMalformedInput(CodingErrorAction.REPORT);

    Ver CodingErrorAction.INFORME

    • He probado este enfoque tratando de caer a través de la codificación UTF-8 a ISO-8859-1 a JISAutoDetect, pero lamentablemente excepciones, no parecen ser lanzado.. (Aunque para UTF-8 error, simplemente los he probado mString.indexOf(‘\ufffd’) != -1 )
    • He añadido .onUnmappableCharacter(CodingErrorAction.REPORT) así y ahora parece lanzar excepciones de la nulidad de las codificaciones.
    • Tenía la esperanza de conseguir CharacterCodingException, pero en mi caso (archivo CSV, lectura UCS-2 lista de materiales como UTF8) no se produce ningún error, pero el archivo es leído como una 1-archivo de carácter. Y la lectura de UTF-8-lista de materiales como UTF-8 no estaba marcado como un error, sin embargo, el contenido era todavía confusa. Por lo que esta técnica es buena y va a coger algunos errores de codificación, pero no es infalible.
  3. 6

    Esto es lo que se utiliza para comprobar la codificación:

    CharsetDecoder ebcdicDecoder = Charset.forName("IBM1047").newDecoder();
    ebcdicDecoder.onMalformedInput(CodingErrorAction.REPORT);
    ebcdicDecoder.onUnmappableCharacter(CodingErrorAction.REPORT);
    
    CharBuffer out = CharBuffer.wrap(new char[3200]);
    CoderResult result = ebcdicDecoder.decode(ByteBuffer.wrap(bytes), out, true);
    if (result.isError() || result.isOverflow() ||
        result.isUnderflow() || result.isMalformed() ||
        result.isUnmappable())
    {
        System.out.println("Cannot decode EBCDIC");
    }
    else
    {
        CoderResult result = ebcdicDecoder.flush(out);
        if (result.isOverflow())
           System.out.println("Cannot decode EBCDIC");
        if (result.isUnderflow())
            System.out.println("Ebcdic decoded succefully ");
    }

    Edit: actualizado con Vouze sugerencia

    • Interesante, habrá un vistazo a eso!
    • Esto funciona si los datos están en un btye matriz, no una Cadena. Por supuesto, esta es la única manera que usted puede poner a prueba la validez, por lo que es perfecta.
    • Advertencia : usted debe llamar decodificador.flush() y el resultado.isUnderflow() es verdadero cuando no se encuentra ningún error.
  4. 4

    Reemplazar todos los caracteres de control en cadena vacía

    value = value.replaceAll("\\p{Cntrl}", "");
    • Guardar mi día a día. El nuevo Android Studio no muestran algunos registros con ctrl
  5. 3

    URLDecoder decodificar a una codificación determinada. Este debe marcar los errores de forma adecuada. Sin embargo, la documentación indica:

    Hay dos posibles formas en que este decodificador no podía hacer frente a la ilegal cadenas. Podría dejar caracteres ilegales solos o podría lanzar un IllegalArgumentException. El enfoque que el decodificador se toma a la izquierda de la aplicación.

    Así que probablemente debería probarlo. Tenga en cuenta también (desde el decode() método de documentación):

    La World Wide Web Consortium Recomendación estados que UTF-8 debe ser utilizado. No hacerlo puede introducir incompatibilites

    así que hay algo más en que pensar!

    EDICIÓN: Apache Commons URLDecode reclamaciones para lanzar excepciones apropiadas para mal codificaciones.

    • Sé de la Recomendación, pero, ¿y el navegador (aquí Firefox 3.0.10) la violan? Como siempre que se recomienda y no se requiere que usted tiene que asegurarse de que no hay ninguna ilegal entidades, ¿no?
    • Así que me gustaría tratar de decodificación mediante el URLDecoder y la elección de la codificación adecuada. Yo estaría interesado (!) a ver si el URLDecoder no lanzar excepciones ilegal de caracteres codificados (fácil comprobar fuera de un navegador/servidor de entorno)
    • Lo siento. Acabo de ver tu editado pregunta re. ilegal chars
    • El Apache Commons enlace está muerto. Parece URLCodec reemplaza URLDecoder: commons.apache.org/proper/commons-codec/apidocs/org/apache/…
  6. 3

    He estado trabajando en un proyecto similar a «adivina la codificación del problema». La mejor solución consiste en saber la codificación. Aparte de eso, usted puede hacer conjeturas para distinguir entre UTF-8 y ISO-8859-1.

    Para responder a la cuestión general de cómo detectar si una cadena está correctamente codificado en UTF-8, puede comprobar lo siguiente:

    1. Ningún byte 0x00, 0xC0, 0xC1, o en el rango 0xF5-0xFF.
    2. Bytes de cola (0x80-0xBF) siempre son precedidos por una cabeza byte 0xC2-0xF4 o de otro byte de cola.
    3. Cabeza bytes debe predecir correctamente el número de bytes de cola (por ejemplo, cualquier byte en 0xC2-0xDF debe ser seguido exactamente un byte en el rango 0x80-0xBF).

    Si una cadena que pasa todas las pruebas, entonces es interpretable como válido UTF-8. Eso no garantiza que es UTF-8, pero es un buen predictor.

    Legal de entrada en ISO-8859-1 probablemente no tienen los caracteres de control (0x00-0x1F y 0x80-0x9F) otro de los separadores de línea. Se parece a 0x7F no está definido en la norma ISO-8859-1.

    (Me estoy basando esta fuera de las páginas de Wikipedia para UTF-8 y ISO-8859-1.)

  7. 2

    Es posible que desee incluir un parámetro conocido en sus peticiones, por ejemplo, «…&encTest=䀻, de forma segura diferenciar entre los diferentes codificaciones.

  8. 1

    Lo que necesita para configurar la codificación de caracteres desde el principio. Intente enviar el correcto Tipo de Contenido, encabezado, por ejemplo, Content-Type: text/html; charset=utf-8 para arreglar la codificación del derecho. El estándar de conformidad se refiere a utf-8 y utf-16, así como la codificación correcta para los Servicios Web. Examinar su encabezados de respuesta.

    También, en el lado del servidor — en el caso que el navegador no maneja correctamente la codificación enviado por el servidor de la fuerza de la codificación mediante la asignación de una nueva Cadena. También puede comprobar cada byte en la codificación utf-8 cadena haciendo un solo each_byte & 0x80, comprobando el resultado como distinto de cero.

    
    boolean utfEncoded = true;
    byte[] strBytes = queryString.getBytes();
    for (int i = 0; i < strBytes.length(); i++) {
        if ((strBytes[i] & 0x80) != 0) {
            continue;
        } else {
            /* treat the string as non utf encoded */
            utfEncoded = false;
            break;
        }
    }
    
    String realQueryString = utfEncoded ?
        queryString : new String(queryString.getBytes(), "iso-8859-1");

    También, tomar un mira en este artículo, espero que le sería de ayuda.

    • de la cadena.getBytes() con new String() es un clásico error que se debe evitar
  9. 1

    la siguiente expresión regular puede ser de interés para usted:

    http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/185624

    Yo lo uso en ruby como la siguiente:

    module Encoding
        UTF8RGX = /\A(
            [\x09\x0A\x0D\x20-\x7E]            # ASCII
          | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
          |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
          | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
          |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
          |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
          | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
          |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
        )*\z/x unless defined? UTF8RGX
    
        def self.utf8_file?(fileName)
          count = 0
          File.open("#{fileName}").each do |l|
            count += 1
            unless utf8_string?(l)
              puts count.to_s + ": " + l
            end
          end
          return true
        end
    
        def self.utf8_string?(a_string)
          UTF8RGX === a_string
        end
    
    end
  10. 0

    Trate de usar UTF-8 por defecto, como siempre, en cualquier lugar se puede tocar. (Base de datos, la memoria y la interfaz de usuario)

    Uno y único juego de caracteres de codificación podría reducir un montón de problemas, y en realidad puede acelerar la velocidad de su rendimiento del servidor web. Hay tantas poder de procesamiento y memoria desperdiciada para la codificación/decodificación.

    • Si bien este es un buen consejo, que no responde a la pregunta. Considere la posibilidad de hacer un comentario en su lugar.

Dejar respuesta

Please enter your comment!
Please enter your name here