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

Dan*_*ler 32 java string unicode encoding

使用场景

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

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

原始问题

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

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

即查询部分看起来像

    ...v=abcädef
Run Code Online (Sandbox Code Playgroud)

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

...v=abc%E4def
Run Code Online (Sandbox Code Playgroud)

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

...v=abc%C3%A4def
Run Code Online (Sandbox Code Playgroud)

期望的解决方案

当我们控制服务时,因为我们已经实现了它,我们想在服务器端检查调用是否包含非utf-8字符,如果是,则以4xx http状态响应

当前的解决方案

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

  1. 如果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;
}
Run Code Online (Sandbox Code Playgroud)

这会捕获所有无效(非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") );
}
Run Code Online (Sandbox Code Playgroud)

这打印:

v=abc?def
v=abcädef
v=abcädef
v=abcädef
Run Code Online (Sandbox Code Playgroud)

它并没有抛出IllegalArgumentException 叹息

ZZ *_*der 32

我问了同样的问题,

在Tomcat上处理URI中的字符编码

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

  1. 将您的URI编码保留为Latin-1.在Tomcat上,将URIEncoding ="ISO-8859-1"添加到server.xml中的Connector.
  2. 如果您必须手动进行URL解码,请将Latin1用作charset.
  3. 使用fixEncoding()函数来修复编码.

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

  String name = fixEncoding(request.getParameter("name"));
Run Code Online (Sandbox Code Playgroud)

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

代码附后.祝好运!

 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;
 }
Run Code Online (Sandbox Code Playgroud)

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

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

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


ant*_*nte 14

您可以使用CharsetDecoder配置为在找到无效字符时抛出异常:

 CharsetDecoder UTF8Decoder =
      Charset.forName("UTF8").newDecoder().onMalformedInput(CodingErrorAction.REPORT);
Run Code Online (Sandbox Code Playgroud)

请参阅CodingErrorAction.REPORT

  • 我添加了`.onUnmappableCharacter(CodingErrorAction.REPORT)`,它现在似乎抛出了无效编码的异常. (2认同)

luc*_*uca 6

这是我用来检查编码的内容:

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 ");
}
Run Code Online (Sandbox Code Playgroud)

编辑:更新了Vouze建议


Zhi*_*Zou 5

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

value = value.replaceAll("\\p{Cntrl}", "");
Run Code Online (Sandbox Code Playgroud)