| 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 | }
|