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