代码之家  ›  专栏  ›  技术社区  ›  Daniel Hiller

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

  •  31
  • Daniel Hiller  · 技术社区  · 15 年前

    使用场景

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

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

    原始问题

    火狐3.0.10似乎尊重浏览器中所选的编码,并根据所选的编码对URL进行编码。这会导致ISO-8859-1和UTF-8的查询字符串不同。

    我们的网站转发来自用户的输入,不转换它(它应该转换),因此它可以通过API调用包含德国umlauts的查询字符串的WebService。

    也就是说,对于看起来像

        ...v=abcädef
    

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

    ...v=abc%E4def
    

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

    ...v=abc%C3%A4def
    

    所需解决方案

    当我们控制服务时,因为我们已经实现了它,所以我们要检查 服务器端 呼叫是否包含非UTF-8字符,如果包含,则以4xx HTTP状态响应

    当前解决方案详情

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

    1. 如果character.getbytes()[0]等于63,则表示“?”
    2. if character.gettype(character.charat(0))返回其他符号

    代码

    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 回复  |  直到 9 年前
        1
  •  31
  •   Community CDub    7 年前

    我问了同样的问题,

    Handling Character Encoding in URI on Tomcat

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

    1. 将URI编码保留为拉丁文-1。在tomcat上,将uriencoding=“iso-8859-1”添加到server.xml中的连接器。
    2. 如果必须手动进行URL解码,也可以使用Latin1作为字符集。
    3. 使用fixEncoding()函数修复编码。

    例如,要从查询字符串中获取参数,

      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那里得到什么。有时你会得到?.others times,您不会得到任何结果,getParameter()返回空值。假设您可以检查“?”,查询字符串包含有效的“?”?

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

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

        2
  •  14
  •   ante    15 年前

    如果发现无效字符,可以使用配置为引发异常的charsetdecoder:

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

    CodingErrorAction.REPORT

        3
  •  4
  •   Taryn Frank Pearson    12 年前

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

    value = value.replaceAll("\\p{Cntrl}", "");
    
        4
  •  4
  •   luca    9 年前

    这是我用来检查编码的:

    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建议更新

        5
  •  3
  •   Brian Agnew    15 年前

    URLDecoder 将解码为给定的编码。这应该适当地标记错误。然而,文件规定:

    解码器有两种可能的方法来处理非法字符串。它可以只保留非法字符,也可以抛出IllegalArgumentException。解码器采用的方法留给实现。

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

    这个 World Wide Web Consortium Recommendation 声明应该使用UTF-8。不这样做可能会导致不兼容

    所以还有别的事情要考虑!

    编辑:Apache Commons URLDecode 声明对错误编码抛出适当的异常。

        6
  •  3
  •   Adrian McCarthy    15 年前

    我一直在研究一个类似的“猜测编码”问题。最佳解决方案包括 知道 编码。除此之外,你可以做出有根据的猜测来区分utf-8和iso-8859-1。

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

    1. 没有字节是0x00、0xC0、0xC1或在0xF5-0xFF范围内。
    2. 尾字节(0x80-0xBF)前面总是有一个头字节0xC2-0xF4或另一个尾字节。
    3. 头字节应正确预测尾字节数(例如,在0xc2-0xdf中的任何字节后应紧跟一个在0x80-0xbf范围内的字节)。

    如果一个字符串通过了所有这些测试,那么它就可以解释为有效的UTF-8。这不能保证 UTF-8,但它是一个很好的预测器。

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

    (我基于维基百科的utf-8和iso-8859-1页面。)

        7
  •  2
  •   mfx    15 年前

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

        8
  •  1
  •   daniel    15 年前

    您需要从一开始就设置字符编码。尝试发送正确的 内容类型 例如,标题 内容类型:text/html;charset=utf-8 修复正确的编码。标准符合性 refers to utf-8 and utf-16 as the proper encoding 对于Web服务。检查您的响应头。

    另外,在服务器端(在浏览器无法正确处理服务器发送的编码的情况下),通过分配新字符串强制编码。此外,您还可以通过执行单个操作来检查编码的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 希望对你有帮助。

        9
  •  1
  •   Skeebl dimus    11 年前

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

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

    我在Ruby中使用它,如下所示:

    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
  •   Dennis C    15 年前

    在任何你能接触到的地方,尽量使用utf-8作为默认值。(数据库、内存和用户界面)

    一个字符集和一个字符集编码可以减少很多问题,实际上它可以提高Web服务器的性能。编码/解码过程中浪费了大量的处理能力和内存。