编码
为什么需要编码
为什么要进行URL编码?通常如果一样东西需要编码,说明这样东西并不适合直接进行传输。
1、会引起歧义:例如 URL 参数字符串中使用 key=value 这样的键值对形式来传参,键值对之间以 & 符号分隔,如 ?postid=5038412&t=1450591802326,服务器会根据参数串的 & 和 = 对参数进行解析,如果 value 字符串中包含了 = 或者 & ,如宝洁公司的简称为P&G,假设需要当做参数去传递,那么可能URL所带参数可能会是这样 ?name=P&G&t=1450591802326,因为参数中多了一个&势必会造成接收 URL 的服务器解析错误,因此必须将引起歧义的 & 和 = 符号进行转义, 也就是对其进行编码。
2、非法字符:又如,URL 的编码格式采用的是 ASCII 码,而不是 Unicode,这也就是说你不能在 URL 中包含任何非 ASCII 字符,例如中文。否则如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成问题。
哪些字符需要编码
-
字母数字
字符 “a” 到 “z”、“A” 到 “Z” 和 “0” 到 “9” 保持不变。 -
特殊字符
“.”、”-“、”*” 和 “_”保持不变。 - 空格字符 ” ” 转换为一个加号 “+”。
-
所有其他字符都是不安全的,因此:
- 首先使用一些编码机制将它们转换为一个或多个字节。
- 然后每个字节用一个包含 3 个字符的字符串 “%xy”表示,其中 xy 为该字节的两位十六进制表示形式。
- 推荐的编码机制是 UTF-8。
编码目的
是将
不安全的字符
转换为安全字符[UTF-8]
比如: 比如:
- http://zh.wikipedia.org/wiki/春节—》http://zh.wikipedia.org/wiki/%E6%98%A5%E8%8A%82,也就是说将”春节”编码成了”%E6%98%A5%E8%8A%82″。==》我们知道,“春”和”节”的utf-8编码分别是”E6 98 A5″和”E8 8A 82”,因此,”%E6%98%A5%E8%8A%82″就是按照顺序,在每个字节前加上%而得到的
怎么编码
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
static unsigned char enc_tab[] = "0123456789ABCDEF";
int main(int argc, char* argv[])
{
const char *str = "中国人";
int len = strlen(str);
for(int i = 0; i < len; i++){
printf("%d, %c, %c\n", (unsigned char)str[i],
enc_tab[(unsigned char)str[i] >> 4], // 取前4个
enc_tab[(unsigned char)str[i] & 0x0F] // 取后4个
);
}
}
完整代码
/**
* URL 编码函数
* @param str {const char*} 源字符串
* @return {char*} 编码后的字符串,返回值不可能为空,需要用 free 释放
*/
static unsigned char enc_tab[] = "0123456789ABCDEF";
char *acl_url_encode(const char *str){
int len = (int) strlen(str);
int tmp_len = len;
unsigned char *tmp = (unsigned char*) malloc(len + 1);
int i, j;
for (i = 0, j = 0; i < len; i++, j++) {
tmp[j] = (unsigned char) str[i];
if (!isalnum(tmp[j]) && strchr("_-.", tmp[j]) == NULL) { // 所传的字符不是字母和数字时,也不是_-.
tmp_len += 3;
tmp = (unsigned char*)realloc(tmp, tmp_len);
tmp[j++] = '%';
tmp[j++] = enc_tab[(unsigned char)str[i] >> 4]; //
tmp[j] = enc_tab[(unsigned char)str[i] & 0x0F];
}
}
tmp[j] = '\0';
return (char*) tmp;
}
使用
const char *str = "中国人";
char * rs = acl_url_encode(str);
printf("%s\n", rs);
free(rs);
解码
既然有编码,就一定有解码。解码是编码的逆过程,因此:
如果我们发现了一个’%’字符,那么接下来的两个字符就是十六进制字符代码。将十六进制代码转换为十进制很简单:
- 第一个字符需要乘以16(<< 4)
- 而另一个字符我们只需从hextable变量中获取值
源码
static unsigned char dec_tab[256] = {
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, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,
0, 10, 11, 12, 13, 14, 15, 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, 10, 11, 12, 13, 14, 15, 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, 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, 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, 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, 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, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
/**
* URL 解码函数
* @param str {const char*} 经URL编码后的字符串
* @return {char*} 解码后的字符串,返回值不可能为空,需要用 free 释放
*/
char *acl_url_decode(const char *str){
int len = (int) strlen(str);
char *tmp = (char *)malloc(len + 1);
int i = 0, pos = 0;
for (i = 0; i < len; i++) {
if (str[i] != '%')
tmp[pos] = str[i];
else if (i + 2 >= len) { /* check boundary */
tmp[pos++] = '%'; /* keep it */
if (++i >= len)
break;
tmp[pos] = str[i];
break;
} else if (isalnum(str[i + 1]) && isalnum(str[i + 2])) {
tmp[pos] = (dec_tab[(unsigned char) str[i + 1]] << 4)
+ dec_tab[(unsigned char) str[i + 2]];
i += 2;
} else
tmp[pos] = str[i];
pos++;
}
tmp[pos] = '\0';
return tmp;
}
测试
const char *str = "中国人";
char * en = acl_url_encode(str);
char *de = acl_url_decode(en);
printf("%s <===> %s", en, de);
free(de);
free(en);