/*-------------------------------------------------------------------------- Base64 mime-header encoding/decoding utility ver 1.02 Written by Kazuyuki HANAAHARA (hanahara@cs.kobe-u.ac.jp) [Encoding] Lines begin with "~s" are mime-encoded. EUC character string is converted into JIS before encoding. [Decoding] Mime-encoded strings in the header part are decoded. No conversion such as JIS to EUC is performed. CAUTION: Encoding/decoding process is performed line by line. --------------------------------------------------------------------------*/ #include #include /* * Base64 encoding/decoding tables. */ #define PAD '=' /* Padding character */ static int et[0x40], /* Encoding table. */ dt[0x100]; /* Decoding table. */ /* * MimeIn, MimeOut. */ #define MI1 "=?ISO-2022-JP?B?" #define MI2 "=?iso-2022-jp?B?" #define MO "?=" #define MILEN 16 #define MOLEN 2 /* * ShiftIn, ShiftOut. */ #define SI1 "\033$B" #define SI2 "\033$@" #define SO1 "\033(B" #define SO2 "\033(J" #define SILEN 3 #define SOLEN 3 /* * Buffer size. */ #define BSIZ 1024 /* * Initialize encoding and decoding tables. */ static void InitTables() { int c, n; /* * Encoding table. */ for( n = 0, c = 'A'; n < 26; n++, c++ ) et[n] = c; for( n = 26, c = 'a'; n < 52; n++, c++ ) et[n] = c; for( n = 52, c = '0'; n < 62; n++, c++ ) et[n] = c; et[62] = '+'; et[63] = '/'; /* * Decoding table. */ for( c = 0; c < 0x100; c++ ) dt[c] = -1; /* '-1' for non-base64 characters. */ for( c = 'A', n = 0; c <= 'Z'; c++, n++ ) dt[c] = n; for( c = 'a', n = 26; c <= 'z'; c++, n++ ) dt[c] = n; for( c = '0', n = 52; c <= '9'; c++, n++ ) dt[c] = n; dt['+'] = 62; dt['/'] = 63; dt[PAD] = 0; /* For simple program. */ } /* * 3 x 8bit (bb[]) --> 4 x 6bit (b[]) * * ( bb[0])( bb[1])( bb[2]) * 765432107654321076543210 * ------------------------ * 543210543210543210543210 * (b[0])(b[1])(b[2])(b[3]) */ static void Encode24bit( bb, b ) int *bb, *b; { b[0] = bb[0] >> 2; b[1] = ((bb[0] & 0x3) << 4) | (bb[1] >> 4); b[2] = ((bb[1] & 0x0f) << 2) | (bb[2] >> 6); b[3] = bb[2] & 0x3f; } /* * Base64 encoding for character string. * ~~~~~~~~~ * Args. so Original string terminated by NULL. * se Encoded string. * * Return Pointer to the end of encoded string 'se'. */ static unsigned char *Base64Encode( so, se ) unsigned char *so, *se; { int b[4], bb[3], i, j; int c; for( j = 0; ; ){ c = *(so++); if( c == NULL || j == 3 ){ /*-- Fill 0 to the left of 24bit to be encoded. --*/ for( i = j; i < 3; i++ ) bb[i] = 0; Encode24bit( bb, b ); for( i = 0; i <= j; i++ ) *(se++) = et[b[i]]; /*-- Add padding characters. --*/ for( ; j < 3; j++ ) *(se++) = PAD; if( c == NULL ) break; j = 0; } bb[j++] = c; } *se = NULL; return( se ); } /* * 4 x 6bit (b[]) --> 3 x 8bit (bb[]) * * ( bb[0])( bb[1])( bb[2]) * 765432107654321076543210 * ------------------------ * 543210543210543210543210 * (b[0])(b[1])(b[2])(b[3]) */ static void Decode24bit( b, bb ) int *b, *bb; { bb[0] = (b[0] << 2) | (b[1] >> 4); bb[1] = ((b[1] & 0x0f) << 4) | (b[2] >> 2); bb[2] = ((b[2] & 0x03) << 6) | b[3]; } /* * Base64 decoding for character string. * ~~~~~~~~~ * Args. so Original (encoded) string. * sd Decoded string terminated by NULL. * * Return Pointer to the end of decoded string 'sd'. * NULL Padding error (at the end of original string). * * Padding error is automatically recovered. * (The correctness is not assured) */ static unsigned char *Base64Decode( so, sd ) unsigned char *so, *sd; { int b[4], bb[3], i, j; int c, pd, ef, fin; for( i = pd = ef = fin = 0; ; ){ c = *(so++); /* * Check end of the code. * (NULL or non-base64 characters) */ if( c == NULL || (c > ' ' && dt[c] < 0) ){ fin = 1; /* Non-base64 character detected. */ if( i == 0 ) break; /* Normal ending. */ else if( i < 4 ) ef = 1; /* Not enough base64 characters. */ } if( fin == 1 || i == 4 ){ /* Fill 0 to the left of 24bit to be decoded. (This part is for input-error-recovery) */ while( i < 4 ){ b[i++] = 0; pd++; } Decode24bit( b, bb ); for( j = 0; j < 3 - pd; j++ ) *(sd++) = bb[j]; if( fin == 1 || pd > 0 ) break; i = 0; } if( c <= ' ' ) continue; /* Skip. */ b[i++] = dt[c]; if( c == PAD ) pd++; /* Padding characters detected. */ } *sd = NULL; return( ef == 1 ? NULL : sd ); } /* * Mime encoding. JIS kanji parts are base64-encoded with MI/MO. * EUC kanji parts are converted to JIS and base64-encoded with MI/MO. * * Args. so Original (mail-header) string. * se Mime-encoded string terminated by NULL. * * Return Pointer to the end of mime-encoded string 'se'. * NULL Format error or buffer over error. */ static unsigned char *MimeEncode( so, se ) unsigned char *so, *se; { static unsigned char buf[BSIZ]; int n, err; err = 0; while( *so != NULL && err == 0 ){ if( *so != '\033' && *so < 0x80 ){ /* * Normal character. */ *(se++) = *(so++); continue; } if( *so >= 0x80 ){ /* * EUC */ strcpy( buf, SI1 ); for( n = SILEN; *so >= 0x80 && n < BSIZ - SOLEN - 2; n++, so++ ){ buf[n] = (*so) & 0x7f; } if( n == BSIZ - SOLEN - 2 ) err = 1; strcpy( buf + n, SO1 ); } else if( strncmp( so, SI1, SILEN ) == 0 || strncmp( so, SI2, SILEN ) == 0 ){ /* * JIS */ strcpy( buf, SI1 ); so += SILEN; for( n = SILEN; n < BSIZ - SOLEN - 2; n++, so++ ){ if( strncmp( so, SO1, SOLEN ) == 0 || strncmp( so, SO2, SOLEN ) == 0 ){ so += SOLEN; break; } if( (buf[n] = (*so)) == NULL ){ err = 1; /* SO not found. */ break; } } if( n == BSIZ - SOLEN - 2 ) err = 1; strcpy( buf + n, SO1 ); } else{ /* * Perhaps some special escape sequence. */ *(se++) = *(so++); continue; } strcpy( se, MI1 ); se += MILEN; se = Base64Encode( buf, se ); strcpy( se, MO ); se += MOLEN; } *se = NULL; return( err == 1 ? NULL : se ); } /* * Recover No-SO(Shift-out) Error in a string. * * Args. s String to be checked. * * Return pointer to the end of the checked (and recovered) string. * * The origineal string is kept intact in the case of no error. */ static unsigned char *RecoverNoSOerr( s ) unsigned char *s; { int flag; for( flag = 0; *s != NULL; ){ if( strncmp( s, SI1, SILEN ) == 0 || strncmp( s, SI2, SILEN ) == 0 ){ /*--- SI detected. ---*/ flag = 1; s += SILEN; } else if( strncmp( s, SO1, SOLEN ) == 0 || strncmp( s, SO2, SOLEN ) == 0 ){ /*--- SO detected. ---*/ flag = 0; s += SOLEN; } else{ s++; } } if( flag == 1 ){ /*--- There is a 'SI' not followed by a 'SO'. ---*/ strcpy( s, SO1 ); /* Recover. */ s += SOLEN; } return( s ); } /* * Mime decoding. * * Args. so Original (mime-encoded mail-header) string. * sd Decoded string terminated by NULL. * * Return Pointer to the end of decoded string 'sd'. * NULL Format error or buffer over error. * * CAUTION: No other code-conversion such like JIS->EUC is performed * in this function. */ static unsigned char *MimeDecode( so, sd ) unsigned char *so, *sd; { static unsigned char buf[BSIZ]; int n, err; err = 0; while( *so != NULL && err == 0 ){ if( *so != '=' || (strncmp( so, MI1, MILEN ) != 0 && strncmp( so, MI2, MILEN ) != 0) ){ /* * Nomal character. */ *(sd++) = *(so++); continue; } /* * Mime base64 encoded string. */ so += MILEN; for( n = 0; n < BSIZ - 1; n++, so++ ){ if( strncmp( so, MO, MOLEN ) == 0 ){ so += MOLEN; break; } if( (buf[n] = (*so)) == NULL ){ err = 1; /* MO not found. */ break; } } if( n == BSIZ - 1) err = 1; buf[n] = NULL; if( Base64Decode( buf, sd ) == NULL ) err = 1; else sd = RecoverNoSOerr( sd ); } if( err == 1 ) return( NULL ); *sd = NULL; return( sd ); } /* * Mime encoding * * Args. fpi, fpo Input and output stream. * * Return 1 Normal. * 0 Format error. (Process is not terminated even in this case) */ int Encoding( fpi, fpo ) FILE *fpi, *fpo; { static unsigned char bufo[BSIZ], bufe[BSIZ * 2]; int ret; ret = 1; while( fgets( bufo, BSIZ, fpi ) != NULL ){ if( strncmp( bufo, "~s", 2 ) != 0 ){ fputs( bufo, fpo ); } else{ if( MimeEncode( bufo, bufe ) == NULL ) ret = 0; fputs( bufe, fpo ); } } return( ret ); } /* * Mime decoding * * Args. fpi, fpo Input and output stream. * h 1 for decoding only header, 0 for all. * * Return 1 Normal. * 0 Format error. (Process is not terminated even in this case) */ int Decoding( fpi, fpo, h ) FILE *fpi, *fpo; int h; { static unsigned char bufo[BSIZ], bufd[BSIZ * 2]; int header, ret; ret = 1; header = 0; while( fgets( bufo, BSIZ, fpi ) != NULL ){ if( strncmp( bufo, "From ", 5 ) == 0 ) header = 1; if( bufo[0] == '\n' ) header = 0; if( header == 0 && h == 1 ){ fputs( bufo, fpo ); } else{ if( MimeDecode( bufo, bufd ) == NULL ) ret = 0; fputs( bufd, fpo ); } } return( ret ); } int main( argc, argv ) int argc; char *argv[]; { FILE *fpi, *fpo; int encode, h, ret; if( argc == 1 || argv[1][0] != '-' ){ fprintf( stderr, "Mime-header encoding/decoding utility.\n" ); fprintf( stderr, "Usage: mimeh -[e|dh|da] [input [output]]\n" ); fprintf( stderr, "-e: encoding, -dh: decoding only header, -da: decoding all\n" ); return( 1 ); } /* * Mode select. */ if( argv[1][1] == 'e' || argv[1][1] == 'E' ){ encode = 1; /* Encoding */ } else{ encode = 0; /* Decoding */ if( argv[1][2] == 'a' || argv[1][2] == 'A' ) h = 0; /* Decoding all. */ else h = 1; /* Decoding only header part. */ } /* * Input and output. */ #if 0 if( argc == 2 ) fprintf( stderr, "---- Waiting input ----\n" ); #endif if( argc >= 3 ) fpi = fopen( argv[2], "r" ); else fpi = stdin; if( argc >= 4 ) fpo = fopen( argv[3], "w" ); else fpo = stdout; if( fpo == NULL || fpi == NULL ){ fprintf( stderr, "Fail to open file(s).\n" ); return( 0 ); } InitTables(); if( encode ){ if( (ret = Encoding( fpi, fpo )) == 0 ){ fprintf( stderr, "Warning: Maybe some format error.\n" ); } } else{ if( (ret = Decoding( fpi, fpo, h )) == 0 ){ fprintf( stderr, "Warning: Maybe some format error.\n" ); } } if( fpi != stdin ) fclose( fpi ); if( fpo != stdout ) fclose( fpo ); return( ret ); }