危险的encodeURIComponent

javascript中的 encodeURIComponent() 方法很常用,MDN里在描述这个方法的时候,有提到这个异常: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent

在下面这个case中,node.js里被发现一个严重bug: http://cnodejs.org/topic/4fd6b7ba839e1e581407aac8
当时所有connect的app都紧急修复大伙们之前都没有意识到这个问题的存在,直到遇到这个可怕的0xDFFF,实在是隐蔽,汗!

stackoverflow上有人给出了一个详细的解答,大致翻译一下,大伙看看:

同样也是遇到这个问题,为了发现问题所在,@Brett Zamir 写了一个遍历方法,把ucs-2的字符集扫描了一遍:

for (var regex = '/[', firstI = null, lastI = null, i = 0; i <= 65535; i++) {
    try {
        encodeURIComponent(String.fromCharCode(i));
    }
    catch(e) {
        if (firstI !== null) {
            if (i === lastI + 1) {
                lastI++;
            }
            else if (firstI === lastI) {
                regex += '\\u' + firstI.toString(16);
                firstI = lastI = i; 
            }
            else {
                regex += '\\u' + firstI.toString(16) + '-' + '\\u' + lastI.toString(16);
                firstI = lastI = i; 
            }
        }
        else {
            firstI = i;
            lastI = i;
        }        
    }
}

if (firstI === lastI) {
    regex += '\\u' + firstI.toString(16);
}
else {
    regex += '\\u' + firstI.toString(16) + '-' + '\\u' + lastI.toString(16);
}
regex += ']/';
alert(regex);  // /[\ud800-\udfff]/

很快结果就出来了,ud800-udfff 这段的字符是有问题的,再写个脚本验证一下:

for (var i = 0; i <= 65535 && (i <0xD800 || i >0xDFFF ) ; i++) {
    try {
        encodeURIComponent(String.fromCharCode(i));
    }
    catch(e) {
        alert(e); // Doesn't alert
    }
}
alert('ok!');

上面的输出符合MSDN上说的:这些字符除了surrogates,即便是“non-characters”也都是合法的unicode序列。

surrogates就是上面扫描出来的危险字符集空间段,分为高位段,低位段

有了这个范围,你可以直接过滤掉这些危险字符,但是当这组字符成对出现的时候(高低位段的字符搭配),它们作为unicode扩展集字符又是合法的(utf-16)

alert(encodeURIComponent('\uD800\uDC00')); // ok
alert(encodeURIComponent('\uD800')); // not ok
alert(encodeURIComponent('\uDC00')); // not ok either

所以如果你只是想屏蔽这个段的字符

urlPart = urlPart.replace(/[\ud800-\udfff]/g, '');

如果你想屏蔽非法的字符,但是保留高低位合法组合的字符(utf-16字符,很少用到),可以这么搞:

function stripUnmatchedSurrogates (str) {
    return str.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, '').split('').reverse().join('').replace(/[\uDC00-\uDFFF](?![\uD800-\uDBFF])/g, '').split('').reverse().join('');
}

var urlPart = '\uD801 \uD801\uDC00 \uDC01'
alert(stripUnmatchedSurrogates(urlPart)); // Leaves one valid sequence (representing a single

如果js native处理了这个问题,就可以免了这个猥琐的修补,多好啊,哼!

====== 翻译完毕 ====

补充:

上诉的高低位端,实际上是 UCS-2中的保留字段。 unicode字符集分为很多端,看这里 http://baike.baidu.com/view/40801.htm

D800-DBFF:High-half zone of UTF-16  utf-16 高位
DC00-DFFF:Low-half zone of UTF-16  utf-16 低位

utf-16的参考资料:http://zh.wikipedia.org/wiki/UTF-16, 其中描述到:

Unicode的编码空间从U+0000到U+10FFFF,共有1,112,064个码位(code point)可用来映射字符. Unicode的编码空间可以划分为17个平面(plane),每个平面包含216(65,536)个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示十六进制值从0016到1016,共计17个平面。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0)。其他平面称为辅助平面(Supplementary Planes)。基本多语言平面内,从U+D800到U+DFFF之间的码位区段是永久保留不映射到Unicode字符。UTF-16就利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。

所以这个问题就清晰明了

补充: 感谢 @猎隼 的补充
decodeURIComponent()确实也是危险的存在, js的encodeURIComponent, decodeURIComponent 处理的都是utf-8编码的字符集。decodeURIComponent 一旦传入gbk的encode字符串,异常就会抛出,没有try catch就会搞死node进程。

参考:
http://stackoverflow.com/questions/16868415/encodeuricomponent-throws-an-exception

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值