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