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