重点述:
定义
MIME
文档传输的
RFC2045
定义了
BASE64
编码。
简要阐述如下:
Base64
编码其实是将
3
个
8
位字节转换为
4
个
6
位字节
,( 3*8 = 4*6 = 24 )
这
4
个六位字节其实仍然是
8
位
,
只不过高两位被设置为
0.
当一个字节只有
6
位有效时
,
它的取值空间为
0
到
2
的
6
次方减
1
即
63,
也就是说被转换的
Base64
编码的每一个编码的取值空间为
(0~63)
。
事实上,
0~63
之间的
ASCII
码有许多不可见字符,所以应该再做一个映射,映射表为
‘A‘ ~ ‘Z‘ ? ASCII
(
0 ~ 25
)
‘a’ ~ ‘z‘ ? ASCII
(
26 ~ 51
)
‘0’ ~ ‘9‘ ? ASCII
(
52 ~ 61
)
‘+‘ ? ASCII
(
62
)
‘/‘ ? ASCII
(
63
)
这样就可以将
3
个
8
位字节,转换为
4
个可见字符。
具体的字节拆分方法为:
aaaaaabb
ccccdddd
eeffffff
字节
1
字节
2
字节
3
转化为:
00aaaaaa 00bbcccc 00ddddee 00ffffff
注:上面的三个字节位原文,下面四个字节为
Base64
编码,其前两位均为
0
。
这样拆分的时候,原文的字节数量应该是
3
的倍数,当这个条件不能满足时,用全零字节补足,转化时
Base64
编码用
=
号代替,这就是为什么有些
Base64
编码以一个或两个等号结束的原因,但等号最多有两个,因为:如果
F(origin)
代表原文的字节数,
F(remain)
代表余数,则
F(remain) = F(origin) MOD 3
成立。
所以
F(remain)
的可能取值为
0,1,2.
如果设
n = [F(origin) – F(remain)] / 3
当
F(remain) = 0
时,恰好转换为
4*n
个字节的
Base64
编码。
当
F(remain) = 1
时,由于一个原文字节可以拆分为属于两个
Base64
编码的字节,为了让
Base64
编码是
4
的倍数,所以应该为补
2
个等号。
当
F(remain) = 2
时,由于两个原文字节可以拆分为属于
3
个
Base64
编码的字节,同理,应该补上一个等号。
具体实现请查看:
#define BASE64_PAD64 '='
char base64_alphabet[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a',
'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
'2', '3', '4', '5', '6', '7', '8', '9', '+',
'/'};
char base64_suffix_map[256] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 62, 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
};
static char cmove_bits(unsigned char src, unsigned lnum, unsigned rnum)
{
src <<= lnum;
src >>= rnum;
return src;
}
char* base64_encode(const char *data)
{
char *ret, *retpos;
int n, m, padnum = 0, retsize, dlen = strlen(data);
if(dlen == 0) return NULL;
/* Account the result buffer size and alloc the memory for it. */
if((dlen % 3) != 0) padnum = 3 - dlen % 3;
retsize = (dlen + padnum) + ((dlen + padnum) * 1/3) + 1;
if((ret = (char*)malloc(retsize)) == NULL) return NULL;
retpos = ret;
for(m = 0; m < (dlen + padnum); m += 3)
{
*(retpos) = base64_alphabet[cmove_bits(*data, 0, 2)];
if(m == dlen + padnum - 3 && padnum != 0)
{ /* Whether the last bits-group suffice 24 bits. */
if(padnum == 1) { /* 16bit need pad one '='. */
*(retpos + 1) = base64_alphabet[cmove_bits(*data, 6, 2) + cmove_bits(*(data + 1), 0, 4)];
*(retpos + 2) = base64_alphabet[cmove_bits(*(data + 1), 4, 2)];
*(retpos + 3) = BASE64_PAD64;
}
else if(padnum == 2)
{ /* 8bit need pad two'='. */
*(retpos + 1) = base64_alphabet[cmove_bits(*data, 6, 2)];
*(retpos + 2) = BASE64_PAD64;
*(retpos + 3) = BASE64_PAD64;
}
else
{ /* 24bit normal. */
*(retpos + 1) = base64_alphabet[cmove_bits(*data, 6, 2) + cmove_bits(*(data + 1), 0, 4)];
*(retpos + 2) = base64_alphabet[cmove_bits(*(data + 1), 4, 2) + cmove_bits(*(data + 2), 0, 6)];
*(retpos + 3) = base64_alphabet[*(data + 2) & 0x3f];
}
retpos += 4;
data += 3;
}
ret[retsize - 1] =0;
return ret;
}
char* base64_decode(const char *bdata)
{
char *ret = NULL, *retpos;
int n, m, padnum = 0, retsize, bdlen = strlen(bdata);
if(bdlen == 0) return NULL; if(bdlen % 4 != 0) return NULL;
/* Whether the data have invalid base-64 characters? */
for(m = 0; m < bdlen; ++m)
{
if((bdata[m] != BASE64_PAD64) && (base64_suffix_map[bdata[m]] == 255))
goto LEND;
}
/* Account the output size. */
if(bdata[bdlen - 1] == '=') padnum = 1;
if(bdata[bdlen - 1] == '=' && bdata[bdlen - 2] == '=') padnum = 2;
retsize = (bdlen - 4) - (bdlen - 4) / 4 + (3 - padnum) + 1;
ret = (char*)malloc(retsize);
if(ret == NULL) return NULL;
retpos = ret;
/* Begging to decode. */
for(m = 0; m < bdlen; m += 4)
{ *retpos = cmove_bits(base64_suffix_map[*bdata], 2, 0) + cmove_bits(base64_suffix_map[*(bdata + 1)], 0, 4);
if(m == bdlen - 4 && padnum != 0)
{ /* Only deal with last four bits. */
if(padnum == 1) /* Have one pad characters, only two availability characters. */
*(retpos + 1) = cmove_bits(base64_suffix_map[*(bdata + 1)], 4, 0) + cmove_bits(base64_suffix_map[*(bdata + 2)], 0, 2); retpos += 3 - padnum;
}
else
{
*(retpos + 1) = cmove_bits(base64_suffix_map[*(bdata + 1)], 4, 0) + cmove_bits(base64_suffix_map[*(bdata + 2)], 0, 2); *(retpos + 2) = cmove_bits(base64_suffix_map[*(bdata + 2)], 6, 0) + base64_suffix_map[*(bdata + 3)];
retpos += 3;
}
bdata += 4;
}
ret[retsize - 1] = 0;
LEND: return ret;
}
设计Base64内容传输编码是为了描述任意的不需要人为识别的字节序列。编码及解码算法很简单,不过,编码后的数据总是比编码前的数据长33%。Base64与RFC1421中定义的Privacy Enhanced Mail (PEM)是同一个编码方法。
注意:这个子集有个很重要的性质,那就是,在任何版本的ISO 646(包括US-ASCII)中,它都被描述成相同的内容。而且,在任何版本的EBCDIC中,子集中的所有字符也都具有相同的描述。其它常用的的编码,如UUENCODE、Macintosh binhex 4.0 [RFC-1741]、base85就没有这些性质,因此就无法满足邮件二进制传输编码的可移植性要求。
编码时,每次输入24位数据,输出为4个编码字符。将输入的3个8位字节从左到右连续排列,就可以形成24位数据。将这24位看做是4个连续的6位组,每组都可以单独译成一个base64表中的字符。当通过base64编码方式编码一个位流时,必须假定位流为“重要位优先”的顺序。就是说,位流中的第1位应该是第一个字节中的最高位;位流中的第8位应该是第一个字节中的最低位,依此累推。
用每组(6位)的值来索引64个可打印字符。索引后将得到的字符依次放入输出字符串中。选择表1中的这些字符,是为了能够完备的描述,并且排除在SMTP中有特殊意义的字符(如‘.’、CF、LF)以及在RFC2046中定义的multipart的边界分隔符中有特殊意义的字符(如‘-’)。
编码输出的流必须被描述成一些不大于76字节的行。解码时,必须要忽略换行符及其它所有不存在于表1中的字符。在base64数据中,除表1中字符、换行符、空格之外的字符都可能表示存在传输错误,在某些情况下,可以适当的给出一些警告信息甚至是拒绝信息。
一定要注意,当base64编码直接应用于未经过规范化的文本内容时,要使用恰当的字节做为换行符。特别是在进行base64编码前,必须要将文本换行符转换为CRLF序列。要注意,一件很重要的事情就是:这些操作可以直接由编码器来完成,而不是在一些实现中先进行一个标准化的步骤。
注释:不用担心multipart实体中的base64编码部分引用到潜在的边界分隔符(boundary delimiter),因为base64编码中没有使用连字符“-”。
7. Content-ID 头字段
在构建一个高级别的用户代理时,可能会需要在一个主体(body)中涉及到另一个主体。因此需要用“Content-ID”头字段给主体(body)设置标签。这个字段在语法构成上与“Message-ID”相同:
Content-ID的值可以被用来在多处上下文中唯一的确定MIME实体,尤其用于被message/external-body机制所引用的缓存数据。虽然Content-ID头字段通常是可选的,但是对于生成可选的媒体类型“message/external-body”的实现程序来说,它则是必须存在的。这就是说,每一个message/external-body实体(entity)必须有一个Content-ID字段来允许对这些数据的缓存。值得注意的是,Content-ID值在multipart/alternative媒体类型中有特殊的意义。这一点会在RFC2046中关于multipart/alternative的那一节中进行解释。