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