如何确定String是否包含无效的编码字符

时间:2009-05-20 10:11:34

标签: java string unicode encoding

使用方案

我们已经实现了一个Web服务,我们的Web前端开发人员在内部使用(通过php api)来显示产品数据。在网站上,用户输入内容(即查询字符串)。在内部,网站通过api调用服务。

注意:我们使用restlet,而不是tomcat

原始问题

Firefox 3.0.10似乎尊重浏览器中选定的编码,并根据所选编码对网址进行编码。这确实导致ISO-8859-1和UTF-8的不同查询字符串。

我们的网站转发来自用户的输入并且不转换它(它应该),因此它可以通过使用包含德语变音符号的查询字符串调用web服务的api来调用服务。

即。查询部分看起来像

    ...v=abcädef

如果选择“ISO-8859-1”,则发送的查询部分看起来像

...v=abc%E4def

但如果选择“UTF-8”,则发送的查询部分看起来像

...v=abc%C3%A4def

所需解决方案

当我们控制服务时,因为我们已经实现了它,我们要检查服务器端,这个调用包含非utf-8字符,如果是,请回复4xx http状态< / p>

当前解决方案

检查每个字符(== string.substring(i,i + 1))

  1. 如果character.getBytes()[0]等于63,则为'?'
  2. 如果Character.getType(character.charAt(0))返回OTHER_SYMBOL
  3. 代码

    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;
    }
    

    问题

    这会捕获所有无效(非utf编码)字符吗? 你们中有谁有更好(更容易)的解决方案吗?

    注意:我使用以下代码检查了URLDecoder

    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") );
    }
    

    打印:

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

    抛出IllegalArgumentException 叹息

10 个答案:

答案 0 :(得分:31)

我问了同样的问题,

Handling Character Encoding in URI on Tomcat

我最近找到了一个解决方案,它对我来说效果很好。你可能想尝试一下。这是你需要做的,

  1. 将您的URI编码保留为Latin-1。在Tomcat上,将URIEncoding =“ISO-8859-1”添加到server.xml中的Connector。
  2. 如果您必须手动进行URL解码,请将Latin1用作字符集。
  3. 使用fixEncoding()函数修复编码。
  4. 例如,要从查询字符串中获取参数,

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

    你总能做到这一点。具有正确编码的字符串不会更改。

    附上代码。祝你好运!

     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;
     }
    

    编辑:您的方法由于各种原因不起作用。当存在编码错误时,您无法依靠从Tomcat获得的内容。有时你得到 或?其他时候,你不会得到任何东西,getParameter()返回null。假设您可以检查“?”,您的查询字符串包含有效“?”的情况会怎样? ?

    此外,您不应拒绝任何请求。这不是您的用户的错。正如我在原始问题中提到的,浏览器可能以UTF-8或Latin-1编码URL。用户无法控制。你需要接受两者。将servlet更改为Latin-1将保留所有字符,即使它们是错误的,也可以让我们有机会修复它或将它丢弃。

    我在这里发布的解决方案并不完美,但它是我们迄今为止发现的最好的解决方案。

答案 1 :(得分:14)

如果找到无效的字符,您可以使用配置为引发异常的CharsetDecoder:

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

请参阅CodingErrorAction.REPORT

答案 2 :(得分:5)

这是我用来检查编码的原因:

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 ");
}

编辑:使用Vouze建议更新

答案 3 :(得分:4)

将所有控制字符替换为空字符串

value = value.replaceAll("\\p{Cntrl}", "");

答案 4 :(得分:3)

URLDecoder将解码为给定的编码。这应该适当地标记错误。但文档说明:

  

这种解码器可以通过两种方式处理非法字符串。它可能会单独留下非法字符,也可能会抛出IllegalArgumentException。解码器采用哪种方法留给实现。

所以你应该尝试一下。另请注意(来自decode()方法文档):

  

World Wide Web Consortium Recommendation表示应该使用UTF-8。不这样做可能会引入不相容性

所以还有别的想法!

编辑:Apache Commons URLDecode声称会为不良编码抛出适当的例外。

答案 5 :(得分:3)

我一直在研究类似的“猜测编码”问题。最佳解决方案涉及了解编码。除此之外,您可以进行有根据的猜测,以区分UTF-8和ISO-8859-1。

要回答如何检测字符串是否正确编码为UTF-8的一般问题,您可以验证以下内容:

  1. 没有字节是0x00,0xC0,0xC1,或者在0xF5-0xFF范围内。
  2. 尾部字节(0x80-0xBF)始终以头字节0xC2-0xF4或另一个尾部字节开头。
  3. 头字节应该正确地预测尾字节数(例如,0xC2-0xDF中的任何字节后面应该跟着0x80-0xBF范围内的一个字节)。
  4. 如果一个字符串通过了所有这些测试,那么它可以解释为有效的UTF-8。这并不能保证它 是UTF-8,但它是一个很好的预测器。

    ISO-8859-1中的合法输入可能没有行分隔符以外的控制字符(0x00-0x1F和0x80-0x9F)。看起来ISO-8859-1中也没有定义0x7F。

    (我的基础是维基百科页面的UTF-8和ISO-8859-1。)

答案 6 :(得分:2)

您可能希望在请求中包含已知参数,例如“...&amp; encTest =䀔,以安全地区分不同的编码。

答案 7 :(得分:1)

您需要从头开始设置字符编码。尝试发送正确的 Content-Type 标头,例如 Content-Type:text / html; charset = utf-8 来修复正确的编码。 Web服务的标准一致性refers to utf-8 and utf-16 as the proper encoding。检查您的回复标题。

此外,在服务器端 - 在浏览器无法正确处理服务器发送的编码的情况下 - 通过分配新的String强制编码。您还可以通过执行单个 each_byte&amp;来检查编码的utf-8字符串中的每个字节。 0x80 ,验证结果为非零。


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");

另外,请look on this article,我希望它可以帮到你。

答案 8 :(得分:1)

您可能会对以下正则表达式感兴趣:

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

我在红宝石中使用它如下:

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

答案 9 :(得分:0)

尝试在您可以触摸的任何地方始终使用UTF-8作为默认设置。 (数据库,内存和UI)

单个字符集编码可以减少很多问题,实际上它可以加快您的Web服务器性能。有很多处理能力和内存浪费在编码/解码上。