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

2022-09-01 07:25:08

使用场景

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

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

原始问题

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

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

即,对于查询部分,如下所示

    ...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. if character.getBytes()[0] 等于 63 表示 '?'
  2. 如果 Character.getType(character.charAt(0)) 返回 OTHER_SYMBOL

法典

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

它不会抛出一个非法的争论异常叹息


答案 1

我问了同样的问题,

处理 Tomcat 上 URI 中的字符编码

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

  1. 将 URI 编码保留为 Latin-1。在 Tomcat 上,将 URIEncoding=“ISO-8859-1” 添加到服务器.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获得的东西。有时你得到或?。其他时候,你不会得到任何东西,getParameter()返回null。假设您可以检查“?”,您的查询字符串包含有效的“?”会发生什么情况?

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

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


答案 2

您可以使用配置为在发现无效字符时引发异常的字符集Decoder:

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

请参阅 CodingErrorAction.REPORT