続・Base64 encoding/decoding (in C)
id:xoinu:20090727 では,C++の標準ストリームのstreambufを拡張してストリームフィルタを作成してみた。これをCで書くとどうなるだろう。残念ながら,ANSI Cには無いけれど,BSD系の標準I/Oライブラリに funopen というのがあって,これを使うと,FILE*を拡張できるようだ(追記:fwopen,fropenというのもあるが,fcloseに対するフックがあるのはfunopenだけっぽい)。
C++のstreambufに相当するのがcookieというオブジェクト。このオブジェクトが,各callbackに渡されるというわけ。非常に分かり易い。
以下がサンプルコード
int main(void) { /* stringstreamに相当するのが無いので,hoge.txtに出力。*/ { FILE* fout; fout = fopen("hoge.txt", "wb"); fprintf(fout, "Xoinu de gonsu.\n"); fclose(fout); } /* foutにbase64フィルタを装着し,sample.txtに出力。*/ { FILE *fout, *fin, *base64; fin = fopen("hoge.txt", "rb"); fout = fopen("sample.txt", "wb"); base64 = base64_fopen(fout, "wb"); pipe_stream(base64, fin); fclose(base64); fclose(fin); } /* finにbase64フィルタを装着し,読み込んで,stderrに垂れ流す。*/ { FILE *fin, *base64; fin = fopen("sample.txt", "rb"); base64 = base64_fopen(fin, "rb"); pipe_stream(stderr, base64); fprintf(stderr, "\n"); fclose(base64); } return 0; }
出力結果:
Xoinu de gonsu.
sample.txt:
WG9pbnUgZGUgZ29uc3UuCg==
C++に比べると,I/Oの拡張に専念できるので分かり易い。C++のstreambufは高機能だけれども,I/Oの抽象化のインターフェースとバッファリングのインターフェースと実装がごにょごにょ混ざっていているのが残念なところ。うんことは言わないが,人気がないのもうなずける。
以下が,C版base64フィルタのソース。BSDライセンスでお楽しみ下さい。
struct base64_buffer { FILE* fp; char buff[4]; int cols; int len; char open_mode; /* 0:read, 1:write */ }; /*---------------------------------------------------------------------------*/ static void base64_encode ( const char* in, int len, char out[4] ) { static const char base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; switch (len) { case 1: out[0] = base64_table[in[0]>>2]; out[1] = base64_table[(in[0]<<4)&63]; out[2] = '='; out[3] = '='; break; case 2: out[0] = base64_table[in[0]>>2]; out[1] = base64_table[((in[0]<<4)|(in[1]>>4))&63]; out[2] = base64_table[(in[1]<<2)&63]; out[3] = '='; break; case 3: out[0] = base64_table[(in[0]>>2)&63]; out[1] = base64_table[((in[0]<<4)|(in[1]>>4))&63]; out[2] = base64_table[((in[1]<<2)|(in[2]>>6))&63]; out[3] = base64_table[in[2]&63]; break; default: out[0] = '='; out[1] = '='; out[2] = '='; out[3] = '='; break; } } /*---------------------------------------------------------------------------*/ static int base64_decode ( const char in[4], char* out, int* length ) { *length = 0; int i; char n; for (i = 0; i < 4; ++i) { n = 0; if ('A' <= in[i] && in[i] <= 'Z') n = (in[i]-'A') ; else if ('a' <= in[i] && in[i] <= 'z') n = (in[i]-'a'+26); else if ('0' <= in[i] && in[i] <= '9') n = (in[i]-'0'+52); else if ('+' == in[i]) n = 62; else if ('/' == in[i]) n = 63; else if ('=' == in[i]) break; else break; switch (i) { case 0: out[0] = n << 2; *length = 1; break; case 1: out[0] |= (n >> 4); out[1] = (n << 4); if (out[1]) *length = 2; break; case 2: out[1] |= (n >> 2); out[2] = (n << 6); if (out[2]) *length = 3; break; case 3: out[2] |= n; *length = 3; break; default: break; } } return 0; } /*---------------------------------------------------------------------------*/ static int base64_writefn ( void* cookie, const char* buff, int len ) { struct base64_buffer* b; const char* end; const char* it; char out[4]; b = cookie; it = buff; end = it + len; while (it != end) { while (it != end && b->len < 3) b->buff[b->len++] = *it++; if (b->len < 3) break; assert(3 == b->len); base64_encode(b->buff, b->len, out); fwrite(out, 1, 4, b->fp); b->len = 0; if (19 == ++b->cols) { fwrite("\n", 1, 1, b->fp); b->cols = 0; } } return it - buff; } /*---------------------------------------------------------------------------*/ static char* base64_decode_chunk ( struct base64_buffer* b, char* buff, int* len ) { char ch, *it, *end, out[3], chunk[4]; int i, ndecoded; it = &chunk[0]; end = &chunk[4]; while (it != end && 1 == fread(&ch, 1, 1, b->fp)) { if ('\n' == ch || ' ' == ch || '\r' == ch) continue; *it++ = ch; } while (it != end) *it++ = '='; base64_decode(chunk, out, &ndecoded); if (*len < ndecoded) { memcpy(buff, out, *len); buff += *len; ndecoded -= *len; memcpy(b->buff, &out[0] + *len, ndecoded); b->len = ndecoded; *len = 0; } else if (ndecoded > 0) { memcpy(buff, out, ndecoded); buff += ndecoded; *len -= ndecoded; } return buff; } /*---------------------------------------------------------------------------*/ static int base64_readfn ( void* cookie, char* buff, int len ) { struct base64_buffer* b; int n, nread; b = cookie; nread = 0; if (len < b->len) { memcpy(buff, b->buff, len); b->len -= len; memmove(b->buff, b->buff + len, b->len); nread = len; } else { memcpy(buff, b->buff, b->len); nread = b->len; len -= b->len; buff += b->len; b->len = 0; while (len > 0) { n = len; buff = base64_decode_chunk(b, buff, &len); n = n - len; if (0 == n) break; nread += n; } } return nread; } /*---------------------------------------------------------------------------*/ static int base64_closefn(void* cookie) { struct base64_buffer* b; b = cookie; if (1 == b->open_mode && b->len) { char out[4]; base64_encode(b->buff, b->len, out); fwrite(out, 1, 4, b->fp); } fclose(b->fp); free(b); return 0; } /*---------------------------------------------------------------------------*/ FILE* base64_fopen(FILE* fp, const char* mode) { struct base64_buffer* cookie; cookie = calloc(1, sizeof(*cookie)); cookie->fp = fp; if ('w' == mode[0]) { cookie->open_mode = 1; return funopen(cookie, NULL, base64_writefn, NULL, base64_closefn); } else { cookie->open_mode = 0; return funopen(cookie, base64_readfn, NULL, NULL, base64_closefn); } } /*---------------------------------------------------------------------------*/ void pipe_stream ( FILE* fout, FILE* fin ) { char buff[1024]; size_t nmemb; for (;;) { nmemb = fread(buff, 1, 1024, fin); fwrite(buff, 1, nmemb, fout); if (nmemb != 1024) break; } }