检测字符串是否以UTF-8双重编码

时间:2011-02-17 17:36:03

标签: language-agnostic unicode utf-8

我需要处理大量的短字符串列表(大多数是俄语,但是任何其他语言都是可能的,包括来自猫在键盘上行走的随机垃圾)。

其中一些字符串将以UTF-8编码两次。

我需要可靠地检测给定字符串是否是双重编码的,并修复它。我应该在不使用任何外部库的情况下执行此操作,只需检查字节即可。检测应该尽可能快。

问题是:如何检测给定字符串是否以UTF-8编码两次?

更新

原始字符串采用UTF-8格式。这是执行第二次编码的AS3代码(遗憾的是我无法控制客户端代码,因此我无法解决此问题):

private function toUTF8(s : String) : String {
       var byteArray : ByteArray = new ByteArray();
       byteArray.writeUTFBytes(s);
       byteArray.position = 0;

       var res : String = "";

       while(byteArray.bytesAvailable){
           res += String.fromCharCode(byteArray.readUnsignedByte());
       }

       return res;
}

myString = toUTF8(("" + myString).toLowerCase().substr(0, 64));

注意toLowerCase()电话。也许这可能会有所帮助?

3 个答案:

答案 0 :(得分:6)

原则上你不能,特别是允许猫垃圾。

在UTF-8编码一次或两次之前,您没有说明数据的原始字符编码是什么。我假设CP1251,(或至少CP1251是其中一种可能性)因为它是一个非常棘手的案例。

取一个非ASCII字符。 UTF-8编码。你得到一些字节,所有这些字节都是CP1251中的有效字符,除非其中一个碰巧是0x98,这是CP1251中唯一的漏洞。

因此,如果将这些字节从CP1251转换为UTF-8,结果与正确UTF-8编码由这些俄语字符组成的CP1251字符串完全相同。没有办法判断结果是对一个字符进行了不正确的双重编码,还是正确地对两个字符进行了单一编码。

如果您对原始数据有一定的控制权,则可以在其开头放置BOM。然后,当它返回给您时,检查初始字节以查看您是否具有UTF-8 BOM,或者错误地对BOM进行双重编码的结果。但我想你可能对原始文本没有那种控制权。

在练习中你可以猜测 - UTF-8解码它然后:

(a)查看字符频率,字符对频率,不可打印字符的数量。这可能允许您暂时声明它是无意义的,因此可能是双重编码的。有了足够的不可打印的字符,它可能是如此荒谬,以至于你甚至不能通过在键盘上捣碎来实际输入它,除非你的ALT键被卡住了。

(b)尝试第二次解码。也就是说,从通过解码UTF-8数据获得的Unicode代码点开始,首先将其编码为CP1251(或其他),然后从UTF-8解码结果。如果任一步骤失败(由于无效的字节序列),那么它肯定不是双重编码的,至少不使用CP1251作为错误的解释。

如果您有一些可能是UTF-8或可能是CP1251的字节,这或多或少都是您所做的,而您不知道哪个。

对于双编码数据无法区分的单编码cat-garbage会产生一些误报,对于双重编码的数据可能只有很少的误报,但是在第一次编码之后仍然看起来像俄罗斯

如果您的原始编码中有比CP1251更多的漏洞,那么您的漏报就会更少。

字符编码很难。

答案 1 :(得分:4)

这是一个适合我的PHP算法。

最好修复你的数据,但如果你不能在这里诀窍:

if ( mb_detect_encoding( utf8_decode( $value ) ) === 'UTF-8' ) {
    // Double encoded, or bad encoding
    $value = utf8_decode( $value );
}

$value = \ForceUTF8\Encoding::toUTF8( $value );

我正在使用的库是: https://github.com/neitanod/forceutf8/

答案 2 :(得分:0)

class InputGroup extends React.Component {

    state = { saveCareersUrlVisible: false } // initial state

    //arrow function 
    inputChangedHandler = (event) => {
        if (event.target.type == 'checkbox') {
            alert('new value : ' + event.target.checked);

            this.setState({ saveCareersUrlVisible: event.target.checked });

        } else {
            alert('new value : ' + event.target.value);
        }
    }

    render() {
        const types = { "Text": 'text', "Date": 'date', "Numeric": "number", "CheckBox": "checkbox" }
        return (
            //The props.value relates to JSX attribute names for the inputgroup below
            <div className="input-groupDynamic">
                <label className="labelD" htmlFor={this.props.label.replace(" ", "-")}>{this.props.label}</label>
                <input name={this.props.label.replace(" ", "-")}
                    type={types[this.props.type]}

                    //uncontrolled components code
                    //defaultValue={props.value}
                    //defaultChecked={props.value}

                    //controlled components code
                    value={this.props.value}
                    checked={this.props.value}
                    onChange={this.inputChangedHandler.bind(this)}
                />
            </div>
        )
    }
}