1/* Base64 encode/decode strings or files. 2 Copyright (C) 2004-2010 Free Software Foundation, Inc. 3 4 This file is part of Base64. 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 18 19/* Written by Simon Josefsson <simon@josefsson.org>. */ 20 21#include <config.h> 22 23#include <stdio.h> 24#include <getopt.h> 25#include <sys/types.h> 26 27#include "system.h" 28#include "error.h" 29#include "xstrtol.h" 30#include "quote.h" 31#include "quotearg.h" 32 33#include "base64.h" 34 35/* The official name of this program (e.g., no `g' prefix). */ 36#define PROGRAM_NAME "base64" 37 38#define AUTHORS proper_name ("Simon Josefsson") 39 40static struct option const long_options[] = 41{ 42 {"decode", no_argument, 0, 'd'}, 43 {"wrap", required_argument, 0, 'w'}, 44 {"ignore-garbage", no_argument, 0, 'i'}, 45 46 {GETOPT_HELP_OPTION_DECL}, 47 {GETOPT_VERSION_OPTION_DECL}, 48 {NULL, 0, NULL, 0} 49}; 50 51void 52usage (int status) 53{ 54 if (status != EXIT_SUCCESS) 55 fprintf (stderr, _("Try `%s --help' for more information.\n"), 56 program_name); 57 else 58 { 59 printf (_("\ 60Usage: %s [OPTION]... [FILE]\n\ 61Base64 encode or decode FILE, or standard input, to standard output.\n\ 62\n"), program_name); 63 fputs (_("\ 64 -w, --wrap=COLS Wrap encoded lines after COLS character (default 76).\n\ 65 Use 0 to disable line wrapping.\n\ 66\n\ 67 -d, --decode Decode data.\n\ 68 -i, --ignore-garbage When decoding, ignore non-alphabet characters.\n\ 69\n\ 70"), stdout); 71 fputs (HELP_OPTION_DESCRIPTION, stdout); 72 fputs (VERSION_OPTION_DESCRIPTION, stdout); 73 fputs (_("\ 74\n\ 75With no FILE, or when FILE is -, read standard input.\n"), stdout); 76 fputs (_("\ 77\n\ 78The data are encoded as described for the base64 alphabet in RFC 3548.\n\ 79When decoding, the input may contain newlines in addition to the bytes of\n\ 80the formal base64 alphabet. Use --ignore-garbage to attempt to recover\n\ 81from any other non-alphabet bytes in the encoded stream.\n"), 82 stdout); 83 emit_ancillary_info (); 84 } 85 86 exit (status); 87} 88 89/* Note that increasing this may decrease performance if --ignore-garbage 90 is used, because of the memmove operation below. */ 91#define BLOCKSIZE 3072 92#define B64BLOCKSIZE BASE64_LENGTH (BLOCKSIZE) 93 94/* Ensure that BLOCKSIZE is a multiple of 3 and 4. */ 95#if BLOCKSIZE % 12 != 0 96# error "invalid BLOCKSIZE" 97#endif 98 99static void 100wrap_write (const char *buffer, size_t len, 101 uintmax_t wrap_column, size_t *current_column, FILE *out) 102{ 103 size_t written; 104 105 if (wrap_column == 0) 106 { 107 /* Simple write. */ 108 if (fwrite (buffer, 1, len, stdout) < len) 109 error (EXIT_FAILURE, errno, _("write error")); 110 } 111 else 112 for (written = 0; written < len;) 113 { 114 uintmax_t cols_remaining = wrap_column - *current_column; 115 size_t to_write = MIN (cols_remaining, SIZE_MAX); 116 to_write = MIN (to_write, len - written); 117 118 if (to_write == 0) 119 { 120 if (fputs ("\n", out) < 0) 121 error (EXIT_FAILURE, errno, _("write error")); 122 *current_column = 0; 123 } 124 else 125 { 126 if (fwrite (buffer + written, 1, to_write, stdout) < to_write) 127 error (EXIT_FAILURE, errno, _("write error")); 128 *current_column += to_write; 129 written += to_write; 130 } 131 } 132} 133 134static void 135do_encode (FILE *in, FILE *out, uintmax_t wrap_column) 136{ 137 size_t current_column = 0; 138 char inbuf[BLOCKSIZE]; 139 char outbuf[B64BLOCKSIZE]; 140 size_t sum; 141 142 do 143 { 144 size_t n; 145 146 sum = 0; 147 do 148 { 149 n = fread (inbuf + sum, 1, BLOCKSIZE - sum, in); 150 sum += n; 151 } 152 while (!feof (in) && !ferror (in) && sum < BLOCKSIZE); 153 154 if (sum > 0) 155 { 156 /* Process input one block at a time. Note that BLOCKSIZE % 157 3 == 0, so that no base64 pads will appear in output. */ 158 base64_encode (inbuf, sum, outbuf, BASE64_LENGTH (sum)); 159 160 wrap_write (outbuf, BASE64_LENGTH (sum), wrap_column, 161 ¤t_column, out); 162 } 163 } 164 while (!feof (in) && !ferror (in) && sum == BLOCKSIZE); 165 166 /* When wrapping, terminate last line. */ 167 if (wrap_column && current_column > 0 && fputs ("\n", out) < 0) 168 error (EXIT_FAILURE, errno, _("write error")); 169 170 if (ferror (in)) 171 error (EXIT_FAILURE, errno, _("read error")); 172} 173 174static void 175do_decode (FILE *in, FILE *out, bool ignore_garbage) 176{ 177 char inbuf[B64BLOCKSIZE]; 178 char outbuf[BLOCKSIZE]; 179 size_t sum; 180 struct base64_decode_context ctx; 181 182 base64_decode_ctx_init (&ctx); 183 184 do 185 { 186 bool ok; 187 size_t n; 188 unsigned int k; 189 190 sum = 0; 191 do 192 { 193 n = fread (inbuf + sum, 1, B64BLOCKSIZE - sum, in); 194 195 if (ignore_garbage) 196 { 197 size_t i; 198 for (i = 0; n > 0 && i < n;) 199 if (isbase64 (inbuf[sum + i]) || inbuf[sum + i] == '=') 200 i++; 201 else 202 memmove (inbuf + sum + i, inbuf + sum + i + 1, --n - i); 203 } 204 205 sum += n; 206 207 if (ferror (in)) 208 error (EXIT_FAILURE, errno, _("read error")); 209 } 210 while (sum < B64BLOCKSIZE && !feof (in)); 211 212 /* The following "loop" is usually iterated just once. 213 However, when it processes the final input buffer, we want 214 to iterate it one additional time, but with an indicator 215 telling it to flush what is in CTX. */ 216 for (k = 0; k < 1 + !!feof (in); k++) 217 { 218 if (k == 1 && ctx.i == 0) 219 break; 220 n = BLOCKSIZE; 221 ok = base64_decode_ctx (&ctx, inbuf, (k == 0 ? sum : 0), outbuf, &n); 222 223 if (fwrite (outbuf, 1, n, out) < n) 224 error (EXIT_FAILURE, errno, _("write error")); 225 226 if (!ok) 227 error (EXIT_FAILURE, 0, _("invalid input")); 228 } 229 } 230 while (!feof (in)); 231} 232 233int 234main (int argc, char **argv) 235{ 236 int opt; 237 FILE *input_fh; 238 const char *infile; 239 240 /* True if --decode has been given and we should decode data. */ 241 bool decode = false; 242 /* True if we should ignore non-base64-alphabetic characters. */ 243 bool ignore_garbage = false; 244 /* Wrap encoded base64 data around the 76:th column, by default. */ 245 uintmax_t wrap_column = 76; 246 247 initialize_main (&argc, &argv); 248 set_program_name (argv[0]); 249 setlocale (LC_ALL, ""); 250 bindtextdomain (PACKAGE, LOCALEDIR); 251 textdomain (PACKAGE); 252 253 atexit (close_stdout); 254 255 while ((opt = getopt_long (argc, argv, "diw:", long_options, NULL)) != -1) 256 switch (opt) 257 { 258 case 'd': 259 decode = true; 260 break; 261 262 case 'w': 263 if (xstrtoumax (optarg, NULL, 0, &wrap_column, NULL) != LONGINT_OK) 264 error (EXIT_FAILURE, 0, _("invalid wrap size: %s"), 265 quotearg (optarg)); 266 break; 267 268 case 'i': 269 ignore_garbage = true; 270 break; 271 272 case_GETOPT_HELP_CHAR; 273 274 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); 275 276 default: 277 usage (EXIT_FAILURE); 278 break; 279 } 280 281 if (argc - optind > 1) 282 { 283 error (0, 0, _("extra operand %s"), quote (argv[optind])); 284 usage (EXIT_FAILURE); 285 } 286 287 if (optind < argc) 288 infile = argv[optind]; 289 else 290 infile = "-"; 291 292 if (STREQ (infile, "-")) 293 input_fh = stdin; 294 else 295 { 296 input_fh = fopen (infile, "r"); 297 if (input_fh == NULL) 298 error (EXIT_FAILURE, errno, "%s", infile); 299 } 300 301 if (decode) 302 do_decode (input_fh, stdout, ignore_garbage); 303 else 304 do_encode (input_fh, stdout, wrap_column); 305 306 if (fclose (input_fh) == EOF) 307 { 308 if (STREQ (infile, "-")) 309 error (EXIT_FAILURE, errno, _("closing standard input")); 310 else 311 error (EXIT_FAILURE, errno, "%s", infile); 312 } 313 314 exit (EXIT_SUCCESS); 315} 316