1/* See LICENSE file for copyright and license details. */
2
3#include "arg.h"
4#include "util.h"
5
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9
10static void
11usage(void)
12{
13 eprintf("usage: %s [-d] [-i] [-w cols] [file]\n", argv0);
14}
15
16static void
17base64_encode(char *dst, const unsigned char *src, size_t len)
18{
19 static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr"
20 "stuvwxyz0123456789+/";
21 size_t i;
22
23 for (i = 0; i < len; i += 3, dst += 4) {
24 unsigned long x = (src[i] & 0xfful) << 16;
25 dst[3] = i + 2 >= len ? '=' : b64[(x |= src[i + 2] & 0xfful) & 0x3f];
26 dst[2] = i + 1 >= len ? '=' : b64[(x |= (src[i + 1] & 0xfful) << 8) >> 6 & 0x3f];
27 dst[1] = b64[x >> 12 & 0x3f];
28 dst[0] = b64[x >> 18];
29 }
30 *dst = '\0';
31}
32
33static size_t
34base64_decode(unsigned char *dst, const char *src)
35{
36 static const char b64[] = {
37 ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6,
38 ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13,
39 ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20,
40 ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27,
41 ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34,
42 ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41,
43 ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48,
44 ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, 53, 54, 55,
45 56, 57, 58, 59, 60, 61, ['+'] = 62,
46 ['/'] = 63, ['='] = 0,
47 };
48 unsigned long x;
49 size_t i, c, len, pad;
50
51 for (i = 0, x = 0, len = 0, pad = 0; src[i]; ++i) {
52 c = (unsigned char)src[i];
53 if (c == '=' && (!src[i + 1] || (src[i + 1] == '=' && !src[i + 2])))
54 ++pad;
55 else if (c >= sizeof(b64) || (!b64[c] && c != 'A'))
56 return 0;
57 x = x << 6 | b64[c];
58 if (i % 4 == 3) {
59 dst[len + 2] = x & 0xff, x >>= 8;
60 dst[len + 1] = x & 0xff, x >>= 8;
61 dst[len + 0] = x & 0xff;
62 len += 3;
63 }
64 }
65 if (i % 4 != 0)
66 return 0;
67 return len - pad;
68}
69
70static void
71encode_stream(FILE *fp, size_t wrap)
72{
73 unsigned char buf[3072];
74 char out[4096 + 1];
75 size_t n, i, col;
76
77 col = 0;
78 while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
79 base64_encode(out, buf, n);
80 for (i = 0; out[i]; i++) {
81 putchar(out[i]);
82 if (wrap > 0 && ++col == wrap) {
83 putchar('\n');
84 col = 0;
85 }
86 }
87 }
88 if (wrap > 0 && col > 0)
89 putchar('\n');
90}
91
92static void
93decode_stream(FILE *fp, int iflag)
94{
95 char in[5];
96 unsigned char out[4];
97 int c;
98 size_t count, n;
99
100 count = 0;
101 while ((c = fgetc(fp)) != EOF) {
102 /* skip whitespace */
103 if (c == '\r' || c == '\n' || c == '\t' || c == ' ')
104 continue;
105 /* check validity */
106 if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '+'
107 || c == '/' || c == '=') {
108 in[count++] = c;
109 if (count == 4) {
110 in[4] = '\0';
111 n = base64_decode(out, in);
112 if (n > 0)
113 fwrite(out, 1, n, stdout);
114 else
115 eprintf("invalid input\n");
116 if (strchr(in, '='))
117 break;
118 count = 0;
119 }
120 } else if (!iflag) {
121 eprintf("invalid character\n");
122 }
123 }
124 if (count > 0)
125 eprintf("input is truncated\n");
126}
127
128// ?man base64: encode or decode base64
129// ?man arguments: file
130// ?man encode or decode data in base64 format
131int
132main(int argc, char *argv[])
133{
134 FILE *fp;
135 int dflag, iflag, ret;
136 size_t wrap;
137
138 dflag = 0;
139 iflag = 0;
140 wrap = 76;
141 ret = 0;
142 fp = stdin;
143
144 ARGBEGIN
145 {
146 // ?man -d: specify directory
147 case 'd':
148 dflag = 1;
149 break;
150 // ?man -i: interactive mode or prompt for confirmation
151 case 'i':
152 iflag = 1;
153 break;
154 // ?man -w:num: wait for completion
155 case 'w':
156 wrap = estrtonum(
157 EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SSIZE_MAX)
158 );
159 break;
160 default:
161 usage();
162 }
163 ARGEND
164
165 if (argc > 1)
166 usage();
167
168 if (argc == 1 && strcmp(argv[0], "-") != 0) {
169 fp = fopen(argv[0], "r");
170 if (!fp)
171 eprintf("fopen %s:", argv[0]);
172 }
173
174 if (dflag)
175 decode_stream(fp, iflag);
176 else
177 encode_stream(fp, wrap);
178
179 if (fp != stdin)
180 fclose(fp);
181
182 ret = fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
183 return ret;
184}