C/C++编程:url编解码实现

  • Post author:
  • Post category:其他




编码



为什么需要编码

为什么要进行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);