b64.c

Nathanial Hendler, 07/07/2009 10:04 PM

Download (17 KB)

 
1
/*********************************************************************\
2
3
MODULE NAME:    b64.c
4
5
AUTHOR:         Bob Trower 08/04/01
6
7
PROJECT:        Crypt Data Packaging
8
9
COPYRIGHT:      Copyright (c) Trantor Standard Systems Inc., 2001
10
11
NOTE:           This source code may be used as you wish, subject to
12
                the MIT license.  See the LICENCE section below.
13
14
DESCRIPTION:
15
                This little utility implements the Base64
16
                Content-Transfer-Encoding standard described in
17
                RFC1113 (http://www.faqs.org/rfcs/rfc1113.html).
18
19
                This is the coding scheme used by MIME to allow
20
                binary data to be transferred by SMTP mail.
21
22
                Groups of 3 bytes from a binary stream are coded as
23
                groups of 4 bytes in a text stream.
24
25
                The input stream is 'padded' with zeros to create
26
                an input that is an even multiple of 3.
27
28
                A special character ('=') is used to denote padding so
29
                that the stream can be decoded back to its exact size.
30
31
                Encoded output is formatted in lines which should
32
                be a maximum of 72 characters to conform to the
33
                specification.  This program defaults to 72 characters,
34
                but will allow more or less through the use of a
35
                switch.  The program enforces a minimum line size
36
                of 4 characters.
37
38
                Example encoding:
39
40
                The stream 'ABCD' is 32 bits long.  It is mapped as
41
                follows:
42
43
                ABCD
44
45
                 A (65)     B (66)     C (67)     D (68)   (None) (None)
46
                01000001   01000010   01000011   01000100
47
48
                16 (Q)  20 (U)  9 (J)   3 (D)    17 (R) 0 (A)  NA (=) NA (=)
49
                010000  010100  001001  000011   010001 000000 000000 000000
50
51
52
                QUJDRA==
53
54
                Decoding is the process in reverse.  A 'decode' lookup
55
                table has been created to avoid string scans.
56
57
DESIGN GOALS:        Specifically:
58
                Code is a stand-alone utility to perform base64 
59
                encoding/decoding. It should be genuinely useful 
60
                when the need arises and it meets a need that is 
61
                likely to occur for some users.  
62
                Code acts as sample code to show the author's 
63
                design and coding style.  
64
65
                Generally: 
66
                This program is designed to survive:
67
                Everything you need is in a single source file.
68
                It compiles cleanly using a vanilla ANSI C compiler.
69
                It does its job correctly with a minimum of fuss.  
70
                The code is not overly clever, not overly simplistic 
71
                and not overly verbose. 
72
                Access is 'cut and paste' from a web page.  
73
                Terms of use are reasonable.  
74
75
VALIDATION:     Non-trivial code is never without errors.  This
76
                file likely has some problems, since it has only
77
                been tested by the author.  It is expected with most
78
                source code that there is a period of 'burn-in' when
79
                problems are identified and corrected.  That being
80
                said, it is possible to have 'reasonably correct'
81
                code by following a regime of unit test that covers
82
                the most likely cases and regression testing prior
83
                to release.  This has been done with this code and
84
                it has a good probability of performing as expected.
85
86
                Unit Test Cases:
87
88
                case 0:empty file:
89
                    CASE0.DAT  ->  ->
90
                    (Zero length target file created
91
                    on both encode and decode.)
92
93
                case 1:One input character:
94
                    CASE1.DAT A -> QQ== -> A
95
96
                case 2:Two input characters:
97
                    CASE2.DAT AB -> QUJD -> AB
98
99
                case 3:Three input characters:
100
                    CASE3.DAT ABC -> QUJD -> ABC
101
102
                case 4:Four input characters:
103
                    case4.dat ABCD -> QUJDRA== -> ABCD
104
105
                case 5:All chars from 0 to ff, linesize set to 50:
106
107
                    AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIj
108
                    JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZH
109
                    SElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWpr
110
                    bG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P
111
                    kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz
112
                    tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX
113
                    2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7
114
                    /P3+/w==
115
116
                case 6:Mime Block from e-mail:
117
                    (Data same as test case 5)
118
119
                case 7: Large files:
120
                    Tested 28 MB file in/out.
121
122
                case 8: Random Binary Integrity:
123
                    This binary program (b64.exe) was encoded to base64,
124
                    back to binary and then executed.
125
126
                case 9 Stress:
127
                    All files in a working directory encoded/decoded
128
                    and compared with file comparison utility to
129
                    ensure that multiple runs do not cause problems
130
                    such as exhausting file handles, tmp storage, etc.
131
132
                -------------
133
134
                Syntax, operation and failure:
135
                    All options/switches tested.  Performs as
136
                    expected.
137
138
                case 10:
139
                    No Args -- Shows Usage Screen
140
                    Return Code 1 (Invalid Syntax)
141
                case 11:
142
                    One Arg (invalid) -- Shows Usage Screen
143
                    Return Code 1 (Invalid Syntax)
144
                case 12:
145
                    One Arg Help (-?) -- Shows detailed Usage Screen.
146
                    Return Code 0 (Success -- help request is valid).
147
                case 13:
148
                    One Arg Help (-h) -- Shows detailed Usage Screen.
149
                    Return Code 0 (Success -- help request is valid).
150
                case 14:
151
                    One Arg (valid) -- Uses stdin/stdout (filter)
152
                    Return Code 0 (Sucess)
153
                case 15:
154
                    Two Args (invalid file) -- shows system error.
155
                    Return Code 2 (File Error)
156
                case 16:
157
                    Encode non-existent file -- shows system error.
158
                    Return Code 2 (File Error)
159
                case 17:
160
                    Out of disk space -- shows system error.
161
                    Return Code 3 (File I/O Error)
162
163
                -------------
164
165
                Compile/Regression test:
166
                    gcc compiled binary under Cygwin
167
                    Microsoft Visual Studio under Windows 2000
168
                    Microsoft Version 6.0 C under Windows 2000
169
170
DEPENDENCIES:   None
171
172
LICENCE:        Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc.
173
174
                Permission is hereby granted, free of charge, to any person
175
                obtaining a copy of this software and associated
176
                documentation files (the "Software"), to deal in the
177
                Software without restriction, including without limitation
178
                the rights to use, copy, modify, merge, publish, distribute,
179
                sublicense, and/or sell copies of the Software, and to
180
                permit persons to whom the Software is furnished to do so,
181
                subject to the following conditions:
182
183
                The above copyright notice and this permission notice shall
184
                be included in all copies or substantial portions of the
185
                Software.
186
187
                THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
188
                KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
189
                WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
190
                PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
191
                OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
192
                OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
193
                OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
194
                SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
195
196
VERSION HISTORY:
197
                Bob Trower 08/04/01 -- Create Version 0.00.00B
198
199
\******************************************************************* */
200
201
#include <stdio.h>
202
#include <stdlib.h>
203
204
/*
205
** Translation Table as described in RFC1113
206
*/
207
static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
208
209
/*
210
** Translation Table to decode (created by author)
211
*/
212
static const char cd64[]="|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq";
213
214
/*
215
** encodeblock
216
**
217
** encode 3 8-bit binary bytes as 4 '6-bit' characters
218
*/
219
void encodeblock( unsigned char in[3], unsigned char out[4], int len )
220
{
221
    out[0] = cb64[ in[0] >> 2 ];
222
    out[1] = cb64[ ((in[0] & 0x03) << 4) | ((in[1] & 0xf0) >> 4) ];
223
    out[2] = (unsigned char) (len > 1 ? cb64[ ((in[1] & 0x0f) << 2) | ((in[2] & 0xc0) >> 6) ] : '=');
224
    out[3] = (unsigned char) (len > 2 ? cb64[ in[2] & 0x3f ] : '=');
225
}
226
227
/*
228
** encode
229
**
230
** base64 encode a stream adding padding and line breaks as per spec.
231
*/
232
void encode( FILE *infile, FILE *outfile, int linesize )
233
{
234
    unsigned char in[3], out[4];
235
    int i, len, blocksout = 0;
236
237
    while( !feof( infile ) ) {
238
        len = 0;
239
        for( i = 0; i < 3; i++ ) {
240
            in[i] = (unsigned char) getc( infile );
241
            if( !feof( infile ) ) {
242
                len++;
243
            }
244
            else {
245
                in[i] = 0;
246
            }
247
        }
248
        if( len ) {
249
            encodeblock( in, out, len );
250
            for( i = 0; i < 4; i++ ) {
251
                putc( out[i], outfile );
252
            }
253
            blocksout++;
254
        }
255
        if( blocksout >= (linesize/4) || feof( infile ) ) {
256
            if( blocksout ) {
257
                fprintf( outfile, "\r\n" );
258
            }
259
            blocksout = 0;
260
        }
261
    }
262
}
263
264
/*
265
** decodeblock
266
**
267
** decode 4 '6-bit' characters into 3 8-bit binary bytes
268
*/
269
void decodeblock( unsigned char in[4], unsigned char out[3] )
270
{   
271
    out[ 0 ] = (unsigned char ) (in[0] << 2 | in[1] >> 4);
272
    out[ 1 ] = (unsigned char ) (in[1] << 4 | in[2] >> 2);
273
    out[ 2 ] = (unsigned char ) (((in[2] << 6) & 0xc0) | in[3]);
274
}
275
276
/*
277
** decode
278
**
279
** decode a base64 encoded stream discarding padding, line breaks and noise
280
*/
281
void decode( FILE *infile, FILE *outfile )
282
{
283
    unsigned char in[4], out[3], v;
284
    int i, len;
285
286
    while( !feof( infile ) ) {
287
        for( len = 0, i = 0; i < 4 && !feof( infile ); i++ ) {
288
            v = 0;
289
            while( !feof( infile ) && v == 0 ) {
290
                v = (unsigned char) getc( infile );
291
                v = (unsigned char) ((v < 43 || v > 122) ? 0 : cd64[ v - 43 ]);
292
                if( v ) {
293
                    v = (unsigned char) ((v == '$') ? 0 : v - 61);
294
                }
295
            }
296
            if( !feof( infile ) ) {
297
                len++;
298
                if( v ) {
299
                    in[ i ] = (unsigned char) (v - 1);
300
                }
301
            }
302
            else {
303
                in[i] = 0;
304
            }
305
        }
306
        if( len ) {
307
            decodeblock( in, out );
308
            for( i = 0; i < len - 1; i++ ) {
309
                putc( out[i], outfile );
310
            }
311
        }
312
    }
313
}
314
315
/*
316
** returnable errors
317
**
318
** Error codes returned to the operating system.
319
**
320
*/
321
#define B64_SYNTAX_ERROR        1
322
#define B64_FILE_ERROR          2
323
#define B64_FILE_IO_ERROR       3
324
#define B64_ERROR_OUT_CLOSE     4
325
#define B64_LINE_SIZE_TO_MIN    5
326
327
/*
328
** b64_message
329
**
330
** Gather text messages in one place.
331
**
332
*/
333
char *b64_message( int errcode )
334
{
335
    #define B64_MAX_MESSAGES 6
336
    char *msgs[ B64_MAX_MESSAGES ] = {
337
            "b64:000:Invalid Message Code.",
338
            "b64:001:Syntax Error -- check help for usage.",
339
            "b64:002:File Error Opening/Creating Files.",
340
            "b64:003:File I/O Error -- Note: output file not removed.",
341
            "b64:004:Error on output file close.",
342
            "b64:004:linesize set to minimum."
343
    };
344
    char *msg = msgs[ 0 ];
345
346
    if( errcode > 0 && errcode < B64_MAX_MESSAGES ) {
347
        msg = msgs[ errcode ];
348
    }
349
350
    return( msg );
351
}
352
353
/*
354
** b64
355
**
356
** 'engine' that opens streams and calls encode/decode
357
*/
358
359
int b64( int opt, char *infilename, char *outfilename, int linesize )
360
{
361
    FILE *infile;
362
    int retcode = B64_FILE_ERROR;
363
364
    if( !infilename ) {
365
        infile = stdin;
366
    }
367
    else {
368
        infile = fopen( infilename, "rb" );
369
    }
370
    if( !infile ) {
371
        perror( infilename );
372
    }
373
    else {
374
        FILE *outfile;
375
        if( !outfilename ) {
376
            outfile = stdout;
377
        }
378
        else {
379
            outfile = fopen( outfilename, "wb" );
380
        }
381
        if( !outfile ) {
382
            perror( outfilename );
383
        }
384
        else {
385
            if( opt == 'e' ) {
386
                encode( infile, outfile, linesize );
387
            }
388
            else {
389
                decode( infile, outfile );
390
            }
391
            if (ferror( infile ) || ferror( outfile )) {
392
                retcode = B64_FILE_IO_ERROR;
393
            }
394
            else {
395
                 retcode = 0;
396
            }
397
            if( outfile != stdout ) {
398
                if( fclose( outfile ) != 0 ) {
399
                    perror( b64_message( B64_ERROR_OUT_CLOSE ) );
400
                    retcode = B64_FILE_IO_ERROR;
401
                }
402
            }
403
        }
404
        if( infile != stdin ) {
405
            fclose( infile );
406
        }
407
    }
408
409
    return( retcode );
410
}
411
412
/*
413
** showuse
414
**
415
** display usage information, help, version info
416
*/
417
void showuse( int morehelp )
418
{
419
    {
420
        printf( "\n" );
421
        printf( "  b64      (Base64 Encode/Decode)      Bob Trower 08/03/01 \n" );
422
        printf( "           (C) Copr Bob Trower 1986-01.      Version 0.00B \n" );
423
        printf( "  Usage:   b64 -option  [ -l num ] [<FileIn> [<FileOut>]]  \n" );
424
        printf( "  Purpose: This program is a simple utility that implements\n" );
425
        printf( "           Base64 Content-Transfer-Encoding (RFC1113).     \n" );
426
    }
427
    if( !morehelp ) {
428
        printf( "           Use -h option for additional help.              \n" );
429
    }
430
    else {
431
        printf( "  Options: -e  encode to Base64   -h  This help text.      \n" );
432
        printf( "           -d  decode from Base64 -?  This help text.      \n" );
433
        printf( "  Note:    -l  use to change line size (from 72 characters)\n" );
434
        printf( "  Returns: 0 = success.  Non-zero is an error code.        \n" );
435
        printf( "  ErrCode: 1 = Bad Syntax, 2 = File Open, 3 = File I/O     \n" );
436
        printf( "  Example: b64 -e binfile b64file     <- Encode to b64     \n" );
437
        printf( "           b64 -d b64file binfile     <- Decode from b64   \n" );
438
        printf( "           b64 -e -l40 infile outfile <- Line Length of 40 \n" );
439
        printf( "  Note:    Will act as a filter, but this should only be   \n" );
440
        printf( "           used on text files due to translations made by  \n" );
441
        printf( "           operating systems.                              \n" );
442
        printf( "  Release: 0.00.00, Tue Aug 7   2:00:00 2001, ANSI-SOURCE C\n" );
443
    }
444
}
445
446
#define B64_DEF_LINE_SIZE   72
447
#define B64_MIN_LINE_SIZE    4
448
449
#define THIS_OPT(ac, av) (ac > 1 ? av[1][0] == '-' ? av[1][1] : 0 : 0)
450
451
/*
452
** main
453
**
454
** parse and validate arguments and call b64 engine or help
455
*/
456
int main( int argc, char **argv )
457
{
458
    int opt = 0;
459
    int retcode = 0;
460
    int linesize = B64_DEF_LINE_SIZE;
461
    char *infilename = NULL, *outfilename = NULL;
462
463
    while( THIS_OPT( argc, argv ) ) {
464
        switch( THIS_OPT(argc, argv) ) {
465
            case 'l':
466
                    linesize = atoi( &(argv[1][2]) );
467
                    if( linesize < B64_MIN_LINE_SIZE ) {
468
                        linesize = B64_MIN_LINE_SIZE;
469
                        printf( "%s\n", b64_message( B64_LINE_SIZE_TO_MIN ) );
470
                    }
471
                    break;
472
            case '?':
473
            case 'h':
474
                    opt = 'h';
475
                    break;
476
            case 'e':
477
            case 'd':
478
                    opt = THIS_OPT(argc, argv);
479
                    break;
480
             default:
481
                    opt = 0;
482
                    break;
483
        }
484
        argv++;
485
        argc--;
486
    }
487
    switch( opt ) {
488
        case 'e':
489
        case 'd':
490
            infilename = argc > 1 ? argv[1] : NULL;
491
            outfilename = argc > 2 ? argv[2] : NULL;
492
            retcode = b64( opt, infilename, outfilename, linesize );
493
            break;
494
        case 0:
495
            retcode = B64_SYNTAX_ERROR;
496
        case 'h':
497
            showuse( opt );
498
            break;
499
500
    }
501
    if( retcode ) {
502
        printf( "%s\n", b64_message( retcode ) );
503
    }
504
505
    return( retcode );
506
}