一、为什么要转义。
以C语言做例子,我想声明一个char字符,该字符表示一个换行。但ASCII码中没有表示换行的文字符号,所以必须用转义字符来表示。于是我们可以这样定义:
char c1=‘\n’;
其中’\n’ 用两个ASCII码字符表示了一个换行符。编译器在遇到反斜杠时,会将其与后面的字符合在一起当作一个字符来处理。
再一种情况,这次我想声明的char字符表示一个单引号(‘)。但是在C语言中单引号是有特殊含义的,两个单引号之间是一个字符。如果中间再加一个单引号,那么编译器就没法正确理解这几个单引号的含义。所以,我们需要如下定义:
char c2=‘\’’;
编译器遇到的第一个单引号是C语法字符,遇到的第二个单引号因为是在反斜杠之后,会被编译器认定是真正的单引号,遇到的第三个单引号同第一个一样,是C语法字符。
第三种情况,因为ASCII码只能表示0-127的字符,那大于128的字符如何表示呢?比如大小为255的字符我么可以这么表示:
char c3=‘\xff’;
现在我们来总结使用转义字符的原因,主要是有两个:
1,有些字符没有文字字符表示,比如ASCII中的空字符、换行符等,还有些字符超出了ASCII码的范围,即那些大于128的字符,这些字符需要用转义字符来表示。
2,某些特定字符在编程语言中有特殊用途,在这些编程语言中就失去了字符原有的含义。如C语言中的引号,HTML中的<等。
二、何时转义
转义的目的有两个:
1,通过转义字符来表示无法表示的内容。
2,通过转义将原本的字符意义改变。在实际使用中处于安全考虑会做。如在执行SQL语句时,若某些数据是外来输入,则需要将这些数据进行转义,以防止注入攻击。
转义操作发生在输入时。为了让输入内容本义改变而做转义操作。实际使用中很多需要在输出时做转义,但这些输出也只是为了成为别人的输入。
三、如何转义
这里我拿web中最常用的三个转义方法来做例子,分别是escape,encodeURI,encodeURIComponent。在此我们用C语言来实现这几个方法,达到对其完全理解的目的。
这三个方法是在web开发中最常用的对字符串编码的函数,均是将特殊字符转换成为%xx格式的编码(xx等于该字符的16进制编码),但还是有些不太一样。
encodeURI,encodeURIComponent:将非ASCII码转为UTF-8格式,然后特殊字符用16进制表示。特殊字符是非ASCII码以及ASCII码中需要转义的字符。两者的区别是字符集的不同。前者比后者要少一点。
escape:先将ASCII码转为UCS-2格式,然后特殊字符用16进制表示。
非ASCII码肯定是需要转义的字符,ASCII码中的待转义字符可以利用Javascript来辅助找到。在Shell中通过以下命令可以得到判断128个ASCII码是否为待转义字符的Javascript代码:
awk 'BEGIN{for(i=0;i<8;i++){for(j=0;j<16;j++){printf("%X\n", i*16+j)}}}' | awk '{p="";if(NR<=16)p="0";printf("console.log(\"%s\",escape(\"\\x%s%s\")==\"\\x%s%s\");\n",$1, p, $1, p, $1)}'
可以得到128行代码:
console.log("0",escape("\x00")=="\x00");
console.log("1",escape("\x01")=="\x01");
console.log("2",escape("\x02")=="\x02");
console.log("3",escape("\x03")=="\x03");
console.log("4",escape("\x04")=="\x04");
console.log("5",escape("\x05")=="\x05");
console.log("6",escape("\x06")=="\x06");
console.log("7",escape("\x07")=="\x07");
console.log("8",escape("\x08")=="\x08");
console.log("9",escape("\x09")=="\x09");
console.log("A",escape("\x0A")=="\x0A");
console.log("B",escape("\x0B")=="\x0B");
console.log("C",escape("\x0C")=="\x0C");
console.log("D",escape("\x0D")=="\x0D");
console.log("E",escape("\x0E")=="\x0E");
console.log("F",escape("\x0F")=="\x0F");
…
在Chrome下面打开控制台,执行以上代码,可以得到如下:
0 false
1 false
2 false
3 false
4 false
5 false
…
其中false代表需要转义的字符,true代表无需转义的字符,这样我就可以得到128个字符中转义成%XX的字符数了。当然为了保险起见,这些被转义的字符是否真的变成%XX,还可以通过这条命令的结果在Javascript中验证一下。
awk 'BEGIN{for(i=0;i<8;i++){for(j=0;j<16;j++){printf("%X\n", i*16+j)}}}' | awk '{p="";if(NR<=16)p="0";printf("if(escape(\"\\x%s%s\")!=\"\\x%s%s\")console.log(\"%s\", escape(\"\\x%s%s\")==\"%%%s%s\");\n", p, $1, p, $1, $1, p, $1, p, $1)}’
经检验,与我们开始的判断完全吻合,大家可以自己试一下。现在,我们用CPP代码来实现escape:
/*!
* \brief 将UCS-2编码文本转义
* \param[in] sUCValue: sUCValue UCS2格式字符串
* \return string 转换后的字符串
*/
std::string escape(const std::string& sUCValue)
{
const static bool s_esc[256] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
};
const T_UC* bpos = (T_UC*)&sUCValue[0];
const T_UC* epos = bpos + (sUCValue.size()/sizeof(T_UC));
std::string sValue = __encodeBase(s_esc, bpos, epos, "%", "%u", "");
return sValue;
}
其中__encodeBase的实现如下:
char* ITOX2(int x, char vx[2])
{
static char MAPX[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
vx[0] = MAPX[(unsigned char)x>>4];
vx[1] = MAPX[x&0x0F];
return vx;
}
char* ITOX4(int x, char vx[4])
{
ITOX2((unsigned short)(x&0xFF00)>>8, vx);
ITOX2(x&0xFF, vx+2);
return vx;
}
std::string __encodeBase(const bool esc[256], const T_UC* bpos, const T_UC* epos, const char* prefix2, const char* prefix4, const char* subfix)
{
int bSize2 = strlen(prefix2);
int bSize4 = strlen(prefix4);
int eSize = strlen(subfix);
char v2[16] = {0};
char v4[16] = {0};
char* p2 = v2+bSize2;
char* p4 = v4+bSize4;
memcpy(v2, prefix2, bSize2);
memcpy(v4, prefix4, bSize4);
memcpy(p2+2, subfix, eSize);
memcpy(p4+4, subfix, eSize);
int s2 = bSize2+2+eSize;
int s4 = bSize4+4+eSize;
std::string sValue((bSize4+eSize+4)*(epos-bpos), 0);
char* tpos = &sValue[0];
while (bpos < epos)
{
if (*bpos & 0xff00)
{
ITOX4(*bpos, p4);
memcpy(tpos, v4, s4);
tpos += s4;
++bpos;
}
else if (esc[*bpos & 0xff])
{
ITOX2(*bpos, p2);
memcpy(tpos, v2, s2);
tpos += s2;
++bpos;
}
else
{
*tpos++ = *bpos++;
}
}
sValue.resize(tpos - &sValue[0]);
return sValue;
}
同理我们来实现encodeURIComponent。先得到表示256个字符是否需要转义的状态map,通过以下结果得到Javascript代码:
awk 'BEGIN{for(i=0;i<8;i++){for(j=0;j<16;j++){printf("%X\n", i*16+j)}}}' | awk '{p="";if(NR<=16)p="0";printf("console.log(\"%s\",encodeURIComponent(\"\\x%s%s\")==\"\\x%s%s\");\n",$1, p, $1, p, $1)}'
CPP代码实现:
/*!
* \brief 将UTF-8编码文本转义
* \param[in] sData: utf-8格式字符串
* \return string 转换后的字符串
*/
std::string encodeURIComponent(const std::string& sData)
{
const static bool s_esc[256] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
};
std::string sValue;
sValue.reserve(sData.size() * 4);
T_UTF8 *bpos = (T_UTF8*)&sData[0];
T_UTF8 *epos = bpos + sData.size();
while (bpos<epos)
{
if (!s_esc[*bpos & 0xff])
{
sValue += *bpos;
}
else
{
char vx[4]={'%', 0, 0, 0};
ITOX2(*bpos, vx+1);
sValue += vx;
}
bpos++;
}
return sValue;
}
两者对应的decode函数分别如下:
/*!
* \brief 将文本解码成utf-8
* \param[in] sData, URIComponent之后的字符串
* \return string 转换后的字符串,utf-8格式
*/
std::string decodeURIComponent(const std::string& sData)
{
std::string sResult = sData;
int x, y;
for (x = 0, y = 0; sData[y]; x++, y++)
{
if((sResult[x] = sData[y]) == '%')
{
sResult[x] = x2c(&sData[y+1]);
y += 2;
}
}
return sResult.substr(0, x);
}
/*!
* \brief 将文本解码成unicode
* \param[in] sData, escape之后的字符串
* \return string 转换后的字符串,unicode格式
*/
std::string unescape(const std::string& sData)
{
struct HEX
{
static bool isHex(T_UC ch)
{
return (ch>='0' && ch<='9' || ch>='A' && ch<='F' || ch>='a' && ch<='f');
}
};
std::string sUCValue = UCS2(sData);
std::string sValue(sUCValue.size(), 0);
T_UC *ucbpos = (T_UC *)&sUCValue[0];
T_UC *ucepos = ucbpos + sUCValue.size()/sizeof(T_UC);
T_UC *ucbResult = (T_UC *)&sValue[0];
#define HEXUCTOI(uc) ((uc >= 'a')? (uc - 'a' + 10) : (uc >= 'A') ? (uc - 'A' + 10) : (uc - '0'))
while (ucbpos < ucepos)
{
if (*ucbpos != 0x25) // %
{
*ucbResult++ = *ucbpos++;
}
else if((*(ucbpos+1)=='u' || *(ucbpos+1)=='U') && (ucepos>ucbpos+5) && HEX::isHex(*(ucbpos+2)) && HEX::isHex(*(ucbpos+3)) && HEX::isHex(*(ucbpos+4)) && HEX::isHex(*(ucbpos+5)))
{
*ucbResult++ = (HEXUCTOI(*(ucbpos+2))<<12) | (HEXUCTOI(*(ucbpos+3))<<8) | (HEXUCTOI(*(ucbpos+4))<<4) | (HEXUCTOI(*(ucbpos+5)));
ucbpos=ucbpos+6;
}
else if ((ucepos>ucbpos+2) && HEX::isHex(*(ucbpos+1)) && HEX::isHex(*(ucbpos+2)) )
{
*ucbResult++ = (HEXUCTOI(*(ucbpos+1))<<4) | (HEXUCTOI(*(ucbpos+2)));
ucbpos=ucbpos+3;
}
else
{
*ucbResult++ = *ucbpos++;
}
}
sValue.resize((ucbResult-(T_UC *)&sValue[0])*sizeof(T_UC));
return sValue;
}
另外,还有json、xml、html的转义,后续再介绍。