Linux工具开发[01]---网络协议中常见编码的原理及其解码函数的实现

时间:2021-12-25 12:19:17

网络协议分析中常见编码的原理及其解码函数

基于C/C++语言实现

本文由CSDN-蚍蜉撼青松 【主页:http://blog.csdn.net/howeverpf】原创,转载请注明出处!

 

一、Base64解码

1.1 简介与编码原理

       在MIME格式的电子邮件中,base64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本。使用时,在传输编码方式中指定base64。使用的字符包括大小写字母各26个,加上10个数字,和加号“+”,斜杠“/”,一共64个字符,等号“=”用来作为后缀用途。完整的base64定义可见RFC 1421和RFC 2045。

----------------------------本段引自*http://zh.wikipedia.org/wiki/Base64

       Base64编码的过程是把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后对于每个6Bit,在其高位再添两位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。在电子邮件中,根据RFC 822规定,每76个字符,还需要加上一个回车换行。由此可以估算编码后数据长度大约为原长的135.1%。

Base64编码的规则

  1. 对输入的原始字节序列,按每3个8bit的字节分为一组,每组共24bit;【三三分组
  2. 针对每组分别处理,将这24bit划分为4个6bit的字节,并在每个6bit的字节前面都填两个高位0,得到4个8bit的字节;【
  3. 将这4个8bit的字节分别转换成10进制,对照Base64编码表 (见图1-1),得到对应编码后的可打印字符;【查表
  4. 每76个字符加一个换行符;【换行处理
  5. 当输入的原始字节序列长度不是3的整数倍时,需在输入字节序列最后用零补齐。在这种情况下,编码后结果必然不足四个字节,需在编码结果最后用'='补齐;这就保证编码后字节序列的长度一定是4的倍数。【结束处理

Linux工具开发[01]---网络协议中常见编码的原理及其解码函数的实现

图1-1 Base64编码表(截自*

 

1.2 Base64解码函数

/***********************************************************/
/*                       输入参数说明:                    */
/*           src为指向存放输入内容的缓冲区指针             */
/*                       输出参数说明:                    */
/*           返回值为指向存放输出内容的缓冲区指针          */
/***********************************************************/
char * Base64_Decode(char *src)
{
    int n,i,j;
    int equal_count=0;//等号的个数
    char *p=NULL;
    char *dst=NULL;
    char  ch64[] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    n=strlen(src);
    if (n%4!=0)//输入不对 返回空指针
    {
        debug(stderr,"Base64_Decode input error");
        return dst;
    }
    if (*(src + n - 1) == '=')
    {
        equal_count += 1;
    }
    if (*(src + n - 2) == '=')
    {
        equal_count += 1;
    }
    for (i=0;i<n;i++)
    {
        p=strchr(ch64,src[i]);//p指向src[i]在ch64中的位置
        if (!p)               //没找到,为"=",不转换
            break;
        src[i]=p-ch64;//将src[i]转换为0-63的数字
    }
    dst=new char[n*3/4+1];
    memset(dst,0,n*3/4+1);
    for (i=0,j=0;i<n-4;i+=4,j+=3)
    {
        dst[j]=(src[i]<<2) | ((src[i+1]&0x30)>>4);
        dst[j+1]=((src[i+1]&0x0F)<<4) | ((src[i+2]&0x3C)>>2);
        dst[j+2]=((src[i+2]&0x03)<<6) | src[i+3];
    }
    switch (equal_count)//最后四个字符单独处理
    {
        case 0:
            dst[j]=(src[n-4]<<2) | ((src[n-3]&0x30)>>4);
            dst[j+1]=((src[n-3]&0x0F)<<4) | ((src[n-2]&0x3C)>>2);
            dst[j+2]=((src[n-2]&0x03)<<6) | src[n-1];
            break;
        case 1:
            dst[j]=(src[n-4]<<2) | ((src[n-3]&0x30)>>4);
            dst[j+1]=((src[n-3]&0x0F)<<4) | ((src[n-2]&0x3C)>>2);
            break;
        case 2:
            dst[j]=(src[n-4]<<2) | ((src[n-3]&0x30)>>4);
            break;
        default:
            break;
    }
    return dst;

 

二、Quoted-Printable解码

2.1 简介与编解码原理

       Quoted-Printable编码,通常缩写为"Q"编码,其原理是把一个8bit的字符用两个16进制数值表示,然后在前面加“=”。所以我们看到经过QP编码后的文件通常是这个样子:=B3=C2=BF=A1=C7=E5=A3= AC=C4=FA=BA=C3=A3=A1。同Base64一样,Quoted-Printable也是MIME邮件中常用的编码方式之一,一般用在电子邮件的标题。

Quoted-Printable的编码规则

  1. QP编码根据输入字符的数值范围进行编码:输入字符在33-60、62-126范围内的,不需编码直接输出;否则先输出'=',后面跟随两个十六进制数字【0–9 或 A–F (必须使用大写)】表示输入字符的十六进制字节值;
  2. ASCII的水平制表符(tab)与空格符, 十进制为9和32,如果不出现在行尾则可以用其ASCII字符编码直接表示;如果这两个字符出现在行尾,必须QP编码表示为"=09" (tab)或"=20" (space);
  3. 如果数据中包含有意义的行结束标志,必须转换为ASCII回车(CR)换行(LF)序列,既不能用原来的ASCII字符也不能用QP编码的"="转义字符序列。 相反,如果字节值13与10有其它的不是行结束的含义,它们必须QP编码为=0D与=0A;
  4. QP编码后的数据要求每行长度不能超过76个字符。为满足此要求又不改变被编码文本,在QP编码结果的每行末尾加上软换行(soft line break). 即在每行末尾加上一个"=\r\n"序列, 但并不会出现在解码得到的文本中。

Quoted-Printable的解码规则:

  1. 碰到 '=' 并且后面2个字符均处于0-9 或 A-F 中时,为编码字符序列,否则为未编码字符;【判定是否为编码后字符】
  2. 对于未编码字符,直接输出;
  3. 对于编码字符序列,要将'='后的两个字符转为十六进制数值,然后求出该十六进制数值对应的十进制值,将得到的十进制值转为对应的编码前字节。

本小节部分内容参考

Quote Printable编码原理(Delphi)》http://blog.csdn.net/fengsh998/article/details/8663046

QP(Quote-Printable) 编码http://love-love-l.blog.163.com/blog/static/2107830420113593541737/

2.2 Quoted-Printable解码函数:

/***********************************************************/
/* 输入参数说明: */
/* pSrc为指向存放输入内容的缓冲区指针 */
/* pDst为指向存放输出内容的缓冲区指针 */
/* nSrcLen为输入缓冲区的大小 */
/* 输出参数说明: */
/* 返回值为输出内容的大小 */
/***********************************************************/
int Quoted_Decode(char* pszSrc, char* pszDst, int nSrclen)
{
int i=0;
int nDstLen; // 输出的字符计数
while (i< nSrclen)
{
if (strncmp(pszSrc, "=\r\n", 3) == 0) // 软回车,跳过
{
pszSrc += 3;
i+= 3;
}
else
{
if (*pszSrc == '=') // 编码字节
{
sscanf(pszSrc, "=%02X", (unsigned int*)pszDst);
pszDst++;
pszSrc += 3;
i+= 3;
}
else // 非编码字节
{
*pszDst++ = *pszSrc++;
i++;
}
nDstLen++;
}
}
*pszDst = '\0'; // 输出加个结束符

return nDstLen;
}

 

三、URL编码解码

3.1 简介与编解码原理

       这种编码方式由于最初用于使URL符合RFC 1738规范【RFC 1738中规定: “只有字母和数字[0-9,a-z,A-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。”】而被称为URL编码,应该算是HTTP请求报文中最常见的编码了。

URL编码的编码规则:

  1. 当输入字节为字母和数字[0-9,a-z,A-Z]、一些特殊符号“$-_.+!*'(),”【不包括双引号】、以及某些保留字时,不需编码直接输出;
  2. 否则先输出'%',后面跟随两个十六进制数字【0–9 或 A–F (大小写皆可) 】表示输入字节的十六进制字节值;

URL编码的解码规则:

  1. 碰到 '%' 并且后面2个字符均处于0-9 或 A-F(大小写皆可)中时,为编码字符序列,否则为未编码字符;【判定是否为编码后字符】
  2. 对于未编码字符,直接输出;
  3. 对于编码字符序列,要将'%'后的两个字符转为十六进制数值,然后求出该十六进制数值对应的十进制值,将得到的十进制值转为对应的编码前字节。

3.2 URL解码函数

/***********************************************************/
/* 输入参数说明: */
/* pSrc为指向存放输入内容的缓冲区指针 */
/* pDst为指向存放输出内容的缓冲区指针 */
/* nSrcLen为输入缓冲区的大小 */
/* 输出参数说明: */
/* 返回值为输出内容的大小 */
/***********************************************************/
int Url_Decode(const char *pSrc, char *pDst, int nSrcLen)
{
int nDstLen; // 输出的字符计数
int i=0;

while(i < nSrcLen)
{
if(*pSrc == '%') // 是编码字节
{
sscanf(pSrc, "%%%02x", (unsigned short *)pDst);

pDst++;
pSrc += 3;
i += 3;
}
else // 非编码字节
{
*pDst++ = (unsigned char )*pSrc++;
i++;
}
nDstLen++;
}
*pDst = '\0'; // 输出加个结束符

return nDstLen;
}

 


------本文由CSDN-蚍蜉撼青松【主页:http://blog.csdn.net/howeverpf】原创,转载请注明出处!------