WvStreams
wvbase64.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  *
00005  * Functions for encoding and decoding strings in MIME's Base64 notation.
00006  *
00007  * Base 64 is pretty easy.  The input is processed in groups of three bytes.
00008  * These 24 bits are split into 4 groups of 6 bits.  Each group of six bits
00009  * is represented by one character in the base64 alphabet, in the encoded
00010  * output.  The alphabet is as follows:
00011  *      ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=
00012  * Where 'A' through '/' represent 000000 through 011111, the 64 different
00013  * combinations.  The '=' (100000) is padding and has no value when decoded.
00014  */
00015 #include "wvbase64.h"
00016 
00017 // maps codes to the Base64 alphabet
00018 static char alphabet[67] =
00019     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n";
00020 
00021 // finds codes in the Base64 alphabet
00022 static int lookup(char ch)
00023 {
00024     if (ch >= 'A' && ch <= 'Z')
00025         return ch - 'A';
00026     if (ch >= 'a' && ch <= 'z')
00027         return ch - 'a' + 26;
00028     if (ch >= '0' && ch <= '9')
00029         return ch - '0' + 52;
00030     if (ch == '+')
00031         return 62;
00032     if (ch == '/')
00033         return 63;
00034     if (ch == '=')
00035         return 64; // padding
00036     if (ch == '\n' || ch == ' ' || ch == '\r' || ch == '\t' ||
00037         ch == '\f' || ch == '\v')
00038         return 65; // whitespace
00039     return -1;
00040 }
00041 
00042 
00043 /***** WvBase64Encoder *****/
00044 
00045 WvBase64Encoder::WvBase64Encoder()
00046 {
00047     _reset();
00048 }
00049 
00050 
00051 bool WvBase64Encoder::_reset()
00052 {
00053     state = ATBIT0;
00054     bits = 0;
00055     return true;
00056 }
00057 
00058 
00059 bool WvBase64Encoder::_encode(WvBuf &in, WvBuf &out, bool flush)
00060 {
00061     // base 64 encode the entire buffer
00062     while (in.used() != 0)
00063     {
00064         unsigned char next = in.getch();
00065         bits = (bits << 8) | next;
00066         switch (state)
00067         {
00068             case ATBIT0:
00069                 out.putch(alphabet[bits >> 2]);
00070                 bits &= 0x03;
00071                 state = ATBIT2;
00072                 break;
00073             case ATBIT2:
00074                 out.putch(alphabet[bits >> 4]);
00075                 bits &= 0x0f;
00076                 state = ATBIT4;
00077                 break;
00078             case ATBIT4:
00079                 out.putch(alphabet[bits >> 6]);
00080                 out.putch(alphabet[bits & 0x3f]);
00081                 bits = 0;
00082                 state = ATBIT0;
00083                 break;
00084         }
00085     }
00086     // do not consider the data flushed if we need padding
00087     if (flush && state != ATBIT0)
00088         return false;
00089     return true;
00090 }
00091 
00092 
00093 bool WvBase64Encoder::_finish(WvBuf &out)
00094 {
00095     // pad text if needed
00096     switch (state)
00097     {
00098         case ATBIT2:
00099             out.putch(alphabet[bits << 4]);
00100             out.putch('=');
00101             out.putch('=');
00102             break;
00103         case ATBIT4:
00104             out.putch(alphabet[bits << 2]);
00105             out.putch('=');
00106             break;
00107         case ATBIT0:
00108             break;
00109     }
00110     return true;
00111 }
00112 
00113 
00114 
00115 /***** WvBase64Decoder *****/
00116 
00117 WvBase64Decoder::WvBase64Decoder()
00118 {
00119     _reset();
00120 }
00121 
00122 
00123 bool WvBase64Decoder::_reset()
00124 {
00125     state = ATBIT0;
00126     bits = 0;
00127     return true;
00128 }
00129 
00130 
00131 bool WvBase64Decoder::_encode(WvBuf &in, WvBuf &out, bool flush)
00132 {
00133     // base 64 decode the entire buffer
00134     while (in.used() != 0)
00135     {
00136         unsigned char next = in.getch();
00137         int symbol = lookup(next);
00138         switch (symbol)
00139         {
00140             case -1: // invalid character
00141                 seterror("invalid character #%s in base64 input", next);
00142                 return false;
00143                 
00144             case 64: // padding
00145                 // strip out any remaining padding
00146                 // we are lenient in that we do not track how much padding we skip
00147                 setfinished();
00148                 state = PAD;
00149                 break;
00150 
00151             case 65: // whitespace
00152                 break;
00153 
00154             default: // other symbol
00155                 bits = (bits << 6) | symbol;
00156                 switch (state)
00157                 {
00158                     case ATBIT0:
00159                         state = ATBIT2;
00160                         break;
00161                     case ATBIT2:
00162                         out.putch(bits >> 4);
00163                         bits &= 0x0f;
00164                         state = ATBIT4;
00165                         break;
00166                     case ATBIT4:
00167                         out.putch(bits >> 2);
00168                         bits &= 0x03;
00169                         state = ATBIT6;
00170                         break;
00171                     case ATBIT6:
00172                         out.putch(bits);
00173                         bits = 0;
00174                         state = ATBIT0;
00175                         break;
00176                         
00177                     case PAD:
00178                         seterror("invalid character #%s "
00179                             "after base64 padding", next);
00180                         return false;
00181                 }
00182                 break;
00183         }
00184     }
00185     // if flushing and we did not get sufficient padding, then fail
00186     if (flush && (state == ATBIT2 || state == ATBIT4 || state == ATBIT6))
00187         return false; // insufficient padding to flush!
00188     return true;
00189 }