背景
今天在使用 siphash 计算字符串 hash 的时候,居然抛异常了。
const siphash = require('siphash');
const Long = require('long');
const cryptoStr = '0123456789ABCDEF';
const hash = input => {
const key = siphash.string16_to_key(cryptoStr);
const { l, h } = siphash.hash(key, input);
const unsigned = true;
const long = new Long(
l,
h,
unsigned,
);
return long;
};
const main = () => {
try {
const r = hash('\udfff');
debugger
} catch (err) {
debugger // URIError: URI malformed
}
};
main();
排查
分析原因,发现是 siphash#string_to_u8 函数中调用了 encodeURIComponent
抛异常了。
在网上查了一些资料后发现 encodeURIComponent 也是会抛异常的,
Note that a
URIError
will be thrown if one attempts to encode a surrogate which is not part of a high-low pair, ...
// high-low pair ok
console.log(encodeURIComponent('\uD800\uDFFF'));
// lone high surrogate throws "URIError: malformed URI sequence"
console.log(encodeURIComponent('\uD800'));
// lone low surrogate throws "URIError: malformed URI sequence"
console.log(encodeURIComponent('\uDFFF'));
解决方案
(1)全转
在我的场景下,确实需要对 \udfff
计算 hash,而且我只需要拿到 '\udfff' 这一串字符就行了。
所以,我用以下函数对抛异常的字符串范围进行了转码,
const safelyEncode = input => input.replace(/[\ud800-\udfff]/g, match => `\\u${match.charCodeAt(0).toString(16)}`);
safelyEncode('\udfff')
> "\udfff"
(2)连续字符转换
如果考虑到只有独立的 high-low pair 字符才是有问题的,
可以使用以下方式进行转码,
// 正则无法从右向左匹配字符串
const reverse = input => input.split('').reverse().join('');
const transform = char => `\\u${char.charCodeAt(0).toString(16)}`;
// 匹配 high-low pair,或者单个 high-low 字符
const regExp = /([\ud800-\udfff][\ud800-\udfff])|([\ud800-\udfff](?![\ud800-\udfff]))/g;
const safelyEncode = input => reverse(
reverse(input).replace(regExp, (match, captureHighLowPair, captureSingleHighLowChar, index, originInput) =>
// 匹配一次 index 往后移动 n 个字符,不匹配就不回调原样输出
captureSingleHighLowChar == null ? captureHighLowPair : reverse(transform(captureSingleHighLowChar))
)
);
'\udfff\ud83c\udfc0'
> "�🏀"
safelyEncode('\udfff\ud83c\udfc0')
> "\udfff🏀"
网友评论