Context
最近在一个项目中,由于转义的问题,导致页面出现报错。检查以后,发现是单引号
引起的,但是转义其实已经做了如下处理:
1 | <div ng-init='content="{%$data.content|replace:"'":'\"'|replace:'"':'\"'%}"'> ... </div> |
奇怪的是,内容中的多个单引号
,有一个单引号
未被替换掉。
Analysis
代码用到的是PHP的smarty模块,这里的replace等同于PHP函数的 str_replace(),即:
1 | str_replace('\'', '\\\'', $data.content); |
这里是没有任何问题,那问题在哪儿呢?事出反常必有妖。那一定是某个特定环境的问题,如是,我想到的是项目中的GBK编码所致。
首先,gbk编码是采用双字节:
GBK字符集范围
GB2312字符集
- 作用:国家简体中文字符集,兼容ASCII。
- 位数:使用2个字节表示,能表示7445个符号,包括6763个汉字,几乎覆盖所有高频率汉字。
- 范围:高字节从A1到F7, 低字节从A1到FE。将高字节和低字节分别加上0XA0即可得到编码。
GBK字符集
- 作用:它是GB2312的扩展,加入对繁体字的支持,兼容GB2312。
- 位数:使用2个字节表示,可表示21886个字符。
- 范围:高字节从81到FE,低字节从40到FE。
分区 | 高位 | 低位 | 说明 |
---|---|---|---|
GBK/1 | A1~A9 | A1~FE | GB2312非汉字符号 |
GBK/2 | B0~F7 | A1~FE | GB2312汉字 |
GBK/3 | 81~A0 | 40~FE | 扩充汉字 |
GBK/4 | AA~FE | 40~A0 | 扩充汉字 |
GBK/5 | A8~A9 | 40~A0 | 扩充非汉字 |
PS:1和2对应的GB2312字符集
ascii编码
- 作用:表语英语及西欧语言。
- 位数:ASCII是用7位表示的,能表示128个字符;其扩展使用8位表示,表示256个字符。
- 范围:ASCII从00到7F,扩展从00到FF
GB2312的编码范围为2121H-777EH,与ASCII有重叠。
str_replace
str_replace的原理是按照ascii编码进行查找替换,不是多字节安全的;GBK编码下的一个经典问题,就是字符替换后的乱码问题;一个例子:
1 | header("Content-type:text/html;charset=GBK"); |
猜猜会输出什么?
过程
- 通过bin2hex($str)转码:
叫你姨
的GBK编码为BDD0 C4E3 D2CC
,心
的GBK编码为D0C4
- 经过str_replace的替换,结果就变成了
BDE3 D2CC
- echo pack(‘N’, hexdec(‘BDE3D2CC’));
结果是:姐姨
continue
回到这个问题,替换前的转码为:
1 | 3c703ed7f7ceaab7f0bdccd2d5caf5c6b7a3acccc6bfa8d3d0c7e5bebbb5c4cff3d5f7d2e2d2e5a3acbeadb9fd266c6471756f3bbfaab9e226726471756f3bb5c4ccc6bfa8d4dab1b3baf3bfc9bfb4b5bd2220cecb22a1a22220b0a122a1a22220bae422a3a8d2f4d2eba3a9c8fdb8f6e8f3cec4d7d6a3acbfaab9e2baf3b5c4ccc6bfa8d2d4b9a9b7eed4dacbc2d4babbf2bcd2d6d0b7f0ccc3ceaad2cba3bbceb4beadbfaab9e2b5c4ccc6bfa8bfc9d7f7ceaad2d5caf5c6b7b0dab9d2d4dabcd2cda5b8c9bebbb4a6a3acc8e7bfcdccfca1a2cae9b7bfa3accac7c7e5bebbc9edd0c4a1a2c6b7ceb6b8dfd1c5b5c4d2d5caf5bcd1c6b7a1a33c6272202f3e4173204275646468697374206172742c205468616e676b612068617320636c65616e20616e6420707572652073796d626f6c697a6174696f6e2e20416674657220226f70656e696e67206c6967687422207468726565205661746963616e207465787420266d646173683b266d646173683b224f6d222c20224168222c2022486f6e672220287472616e736c697465726174696f6e292063616e206265207365656e20696e20746865206261636b67726f756e64206f66207468616e676b612e205468656e207468616e676b612073686f756c6420626520617070726f7072696174656c7920776f727368697070656420696e207468652074656d706c65206f7220612066616d696c792068616c6c20666f7220776f727368697070696e67204275646468612e20576974686f7574206265696e67206f70656e6564206c696768742c207468616e676b612063616e2062652068616e67656420696e206120636c65616e20706c6163652c2073756368206173206c6976696e6720726f6f6d206f722073747564792e205468616e676b612069732074686520617274206f6620656c6567616e742074617374652077686963682063616e20616c736f2068656c7020707572696679206d656e74616c6974792e3c2f703e |
替换后的转码为:
1 | 3c703ed7f7ceaab7f0bdccd2d5caf5c6b7a3acccc6bfa8d3d0c7e5bebbb5c4cff3d5f7d2e2d2e5a3acbeadb9fd266c6471756f3bbfaab9e226726471756f3bb5c4ccc6bfa8d4dab1b3baf3bfc9bfb4b5bd5c2220cecb5c22a1a25c2220b0a15c22a1a25c2220bae422a3a8d2f4d2eba3a9c8fdb8f6e8f3cec4d7d6a3acbfaab9e2baf3b5c4ccc6bfa8d2d4b9a9b7eed4dacbc2d4babbf2bcd2d6d0b7f0ccc3ceaad2cba3bbceb4beadbfaab9e2b5c4ccc6bfa8bfc9d7f7ceaad2d5caf5c6b7b0dab9d2d4dabcd2cda5b8c9bebbb4a6a3acc8e7bfcdccfca1a2cae9b7bfa3accac7c7e5bebbc9edd0c4a1a2c6b7ceb6b8dfd1c5b5c4d2d5caf5bcd1c6b7a1a33c6272202f3e4173204275646468697374206172742c205468616e676b612068617320636c65616e20616e6420707572652073796d626f6c697a6174696f6e2e204166746572205c226f70656e696e67206c696768745c22207468726565205661746963616e207465787420266d646173683b266d646173683b5c224f6d5c222c205c2241685c222c205c22486f6e675c2220287472616e736c697465726174696f6e292063616e206265207365656e20696e20746865206261636b67726f756e64206f66207468616e676b612e205468656e207468616e676b612073686f756c6420626520617070726f7072696174656c7920776f727368697070656420696e207468652074656d706c65206f7220612066616d696c792068616c6c20666f7220776f727368697070696e67204275646468612e20576974686f7574206265696e67206f70656e6564206c696768742c207468616e676b612063616e2062652068616e67656420696e206120636c65616e20706c6163652c2073756368206173206c6976696e6720726f6f6d206f722073747564792e205468616e676b612069732074686520617274206f6620656c6567616e742074617374652077686963682063616e20616c736f2068656c7020707572696679206d656e74616c6974792e3c2f703e |
即将22
替换为5c22
,替换结果中确实有一处的22
未被替换。而这个22
就是导致报错的问题了,为啥没有替换,猜想难道是22
在str_replace的时候被前后分开了,但并没有…
Why?
在本地环境中,通过对上面的编码,进行翻转,未能复现线上的效果,这是啥原因呢?
1 |
|
解决方案
这类的问题解决方案其实也很多,比如:
- 将内容转换为UTF-8,进行替换,替换完了,再转成GBK;
- 使用双字节可靠的mb_ereg_replace进行替换;