master xplshn/aruu / cmd / pseudo / base64.c
  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}