master xplshn/aruu / cmd / posix / uudecode.c
  1/* See LICENSE file for copyright and license details. */
  2
  3
  4#include <sys/stat.h>
  5
  6#include <errno.h>
  7#include <stdio.h>
  8#include <stdlib.h>
  9#include <string.h>
 10
 11#include "util.h"
 12
 13static int mflag = 0;
 14static int oflag = 0;
 15
 16static FILE *
 17parsefile(const char *fname)
 18{
 19	struct stat st;
 20	int ret;
 21
 22	if (!strcmp(fname, "/dev/stdout") || !strcmp(fname, "-"))
 23		return stdout;
 24	ret = lstat(fname, &st);
 25	/* if it is a new file, try to open it */
 26	if (ret < 0 && errno == ENOENT)
 27		goto tropen;
 28	if (ret < 0) {
 29		weprintf("lstat %s:", fname);
 30		return NULL;
 31	}
 32	if (!S_ISREG(st.st_mode)) {
 33		weprintf("for safety uudecode operates only on regular files and /dev/stdout\n");
 34		return NULL;
 35	}
 36tropen:
 37	return fopen(fname, "w");
 38}
 39
 40static void
 41parseheader(FILE *fp, const char *s, char **header, mode_t *mode, char **fname)
 42{
 43	static char bufs[PATH_MAX + 18]; /* len header + mode + maxname */
 44	char *p, *q;
 45	size_t n;
 46
 47	if (!fgets(bufs, sizeof(bufs), fp))
 48		if (ferror(fp))
 49			eprintf("%s: read error:", s);
 50	if (bufs[0] == '\0' || feof(fp))
 51		eprintf("empty or nil header string\n");
 52	if (!(p = strchr(bufs, '\n')))
 53		eprintf("header string too long or non-newline terminated file\n");
 54	p = bufs;
 55	if (!(q = strchr(p, ' ')))
 56		eprintf("malformed mode string in header, expected ' '\n");
 57	*header = bufs;
 58	*q++ = '\0';
 59	p = q;
 60	/* now header should be null terminated, q points to mode */
 61	if (!(q = strchr(p, ' ')))
 62		eprintf("malformed mode string in header, expected ' '\n");
 63	*q++ = '\0';
 64	/* now mode should be null terminated, q points to fname */
 65	*mode = parsemode(p, *mode, 0);
 66	n = strlen(q);
 67	while (n > 0 && (q[n - 1] == '\n' || q[n - 1] == '\r'))
 68		q[--n] = '\0';
 69	if (n > 0)
 70		*fname = q;
 71	else
 72		eprintf("header string does not contain output file\n");
 73}
 74
 75static const char b64dt[] = {
 76	-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
 77	-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
 78	52,53,54,55,56,57,58,59,60,61,-1,-1,-1, 0,-1,-1,-1, 0, 1, 2, 3, 4, 5, 6,
 79	 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
 80	-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,
 81	49,50,51,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
 82	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
 83	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
 84	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
 85	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
 86	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
 87};
 88
 89static void
 90uudecodeb64(FILE *fp, FILE *outfp)
 91{
 92	char bufb[60], *pb;
 93	char out[45], *po;
 94	size_t n;
 95	int b = 0, e, t = -1, l = 1;
 96	unsigned char b24[3] = {0, 0, 0};
 97
 98	while ((n = fread(bufb, 1, sizeof(bufb), fp))) {
 99		for (pb = bufb, po = out; pb < bufb + n; pb++) {
100			if (*pb == '\n') {
101				l++;
102				continue;
103			} else if (*pb == '=') {
104				switch (b) {
105				case 0:
106					/* expected '=' remaining
107					 * including footer */
108					if (--t) {
109						fwrite(out, 1,
110						       (po - out),
111						       outfp);
112						return;
113					}
114					continue;
115				case 1:
116					eprintf("%d: unexpected \"=\""
117					        "appeared\n", l);
118					/* fallthrough */
119				case 2:
120					*po++ = b24[0];
121					b = 0;
122					t = 5; /* expect 5 '=' */
123					continue;
124				case 3:
125					*po++ = b24[0];
126					*po++ = b24[1];
127					b = 0;
128					t = 6; /* expect 6 '=' */
129					continue;
130				}
131			} else if ((e = b64dt[(int)*pb]) == -1)
132				eprintf("%d: invalid byte \"%c\"\n", l, *pb);
133			else if (e == -2) /* whitespace */
134				continue;
135			else if (t > 0) /* state is parsing pad/footer */
136				eprintf("%d: invalid byte \"%c\""
137					" after padding\n",
138				        l, *pb);
139			switch (b) { /* decode next base64 chr based on state */
140				case 0: b24[0] |= e << 2; break;
141				case 1: b24[0] |= (e >> 4) & 0x3;
142				        b24[1] |= (e & 0xf) << 4; break;
143				case 2: b24[1] |= (e >> 2) & 0xf;
144				        b24[2] |= (e & 0x3) << 6; break;
145				case 3: b24[2] |= e; break;
146			}
147			if (++b == 4) { /* complete decoding an octet */
148				*po++ = b24[0];
149				*po++ = b24[1];
150				*po++ = b24[2];
151				b24[0] = b24[1] = b24[2] = 0;
152				b = 0;
153			}
154		}
155		fwrite(out, 1, (po - out), outfp);
156	}
157	eprintf("%d: invalid uudecode footer \"====\" not found\n", l);
158}
159
160static void
161uudecode(FILE *fp, FILE *outfp)
162{
163	char *bufb = NULL, *p;
164	size_t n = 0;
165	ssize_t len;
166	int ch, i;
167
168#define DEC(c)  (((c) - ' ') & 077) /* single character decode */
169#define IS_DEC(c) ( (((c) - ' ') >= 0) && (((c) - ' ') <= 077 + 1) )
170#define OUT_OF_RANGE(c) eprintf("character %c out of range: [%d-%d]\n", (c), 1 + ' ', 077 + ' ' + 1)
171
172	while ((len = getline(&bufb, &n, fp)) > 0) {
173		p = bufb;
174		/* trim newlines */
175		if (!len || bufb[len - 1] != '\n')
176			eprintf("no newline found, aborting\n");
177		bufb[len - 1] = '\0';
178
179		/* check for last line */
180		if ((i = DEC(*p)) <= 0)
181			break;
182		for (++p; i > 0; p += 4, i -= 3) {
183			if (i >= 3) {
184				if (!(IS_DEC(*p) && IS_DEC(*(p + 1)) &&
185				      IS_DEC(*(p + 2)) && IS_DEC(*(p + 3))))
186					OUT_OF_RANGE(*p);
187
188				ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
189				putc(ch, outfp);
190				ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
191				putc(ch, outfp);
192				ch = DEC(p[2]) << 6 | DEC(p[3]);
193				putc(ch, outfp);
194			} else {
195				if (i >= 1) {
196					if (!(IS_DEC(*p) && IS_DEC(*(p + 1))))
197						OUT_OF_RANGE(*p);
198
199					ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4;
200					putc(ch, outfp);
201				}
202				if (i >= 2) {
203					if (!(IS_DEC(*(p + 1)) &&
204					      IS_DEC(*(p + 2))))
205						OUT_OF_RANGE(*p);
206
207					ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2;
208					putc(ch, outfp);
209				}
210			}
211		}
212		if (ferror(fp))
213			eprintf("read error:");
214	}
215	/* check for end or fail */
216	if ((len = getline(&bufb, &n, fp)) < 0)
217		eprintf("getline:");
218	if (len < 3 || strncmp(bufb, "end", 3) || bufb[3] != '\n')
219		eprintf("invalid uudecode footer \"end\" not found\n");
220	free(bufb);
221}
222
223static void
224usage(void)
225{
226	eprintf("usage: %s [-m] [-o output] [file]\n", argv0);
227}
228
229// ?man uudecode: decode uuencoded file
230// ?man arguments: file
231// ?man decode a file created by uuencode
232int
233main(int argc, char *argv[])
234{
235	FILE *fp = NULL, *nfp = NULL;
236	mode_t mode = 0;
237	int ret = 0;
238	char *fname, *header, *ifname, *ofname = NULL;
239	void (*d) (FILE *, FILE *) = NULL;
240
241	ARGBEGIN {
242	// ?man -m: specify mode or limit
243	case 'm':
244		mflag = 1; /* accepted but unused (autodetect file type) */
245		break;
246	// ?man -o:str: specify output file
247	case 'o':
248		oflag = 1;
249		ofname = EARGF(usage());
250		break;
251	default:
252		usage();
253	} ARGEND
254
255	if (argc > 1)
256		usage();
257
258	if (!argc || !strcmp(argv[0], "-")) {
259		fp = stdin;
260		ifname = "<stdin>";
261	} else {
262		if (!(fp = fopen(argv[0], "r")))
263			eprintf("fopen %s:", argv[0]);
264		ifname = argv[0];
265	}
266
267	parseheader(fp, ifname, &header, &mode, &fname);
268
269	if (!strncmp(header, "begin", sizeof("begin")))
270		d = uudecode;
271	else if (!strncmp(header, "begin-base64", sizeof("begin-base64")))
272		d = uudecodeb64;
273	else
274		eprintf("unknown header %s:", header);
275
276	if (oflag)
277		fname = ofname;
278	if (!(nfp = parsefile(fname)))
279		eprintf("fopen %s:", fname);
280
281	d(fp, nfp);
282
283	if (nfp != stdout && chmod(fname, mode) < 0)
284		eprintf("chmod %s:", fname);
285
286	ret |= fshut(fp, (fp == stdin) ? "<stdin>" : argv[0]);
287	ret |= fshut(nfp, (nfp == stdout) ? "<stdout>" : fname);
288
289	return ret;
290}