This is uudecode.c in view mode; [Download] [Up]
/* Copyright (c) 1995 John E. Davis (davis@space.mit.edu) * All rights reserved. */ #include "config.h" #include "features.h" #include <stdio.h> #include <string.h> #include <ctype.h> #include <sys/types.h> #include <sys/stat.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifdef HAVE_STDLIB_H # include <stdlib.h> #endif #define MAX_ARTICLE_LINE_LEN 4096 #include "uudecode.h" #ifdef STANDALONE # define SLRN_ERROR(x) fprintf(stderr, (x)) # define SLRN_MESSAGE(x) fprintf(stdout, (x)) # define SLRN_ERROR_1(x,y) fprintf(stderr, (x), (y)) # define SLRN_MESSAGE_1(x,y) fprintf(stdout, (x), (y)) # define SLRN_MESSAGE_2(x,y,z) fprintf(stdout, (x), (y), (z)) # define SLRN_POPEN popen # define SLRN_PCLOSE pclose # define slrn_fclose slrn_fclose_standalone # define UPPER_CASE(x) ((x) >= 'a') && ((x) <= 'z') ? (x) - 32 : (x) # define LOWER_CASE(x) ((x) >= 'A') && ((x) <= 'Z') ? (x) + 32 : (x) static int slrn_fclose_standalone (FILE *fp) { if (0 == fclose (fp)) return 0; SLRN_ERROR ("Error closing file. File system full?"); return -1; } #else # include <slang.h> # include "jdmacros.h" # include "slrn.h" # include "misc.h" # include "uudecode.h" # define SLRN_ERROR slrn_error # define SLRN_ERROR_1 slrn_error # define SLRN_MESSAGE(x) slrn_message(x); slrn_refresh () # define SLRN_MESSAGE_1(x,y) slrn_message((x), (y)); slrn_smg_refresh () # define SLRN_MESSAGE_2(x,y,z) slrn_message((x), (y), (z)); slrn_smg_refresh () # define SLRN_POPEN slrn_popen # define SLRN_PCLOSE slrn_pclose char *Slrn_Decode_Directory; #endif /* Note!!! These routines assume a flat address space !! */ int slrn_case_strncmp (unsigned char *a, register unsigned char *b, register unsigned int n) { register unsigned char cha, chb, *bmax; register int diff = a - b; bmax = b + n; while (b < bmax) { cha = UPPER_CASE(b[diff]); chb = UPPER_CASE(*b); if (cha != chb) { return (int) cha - (int) chb; } else if (chb == 0) return 0; b++; } return 0; } #ifndef STANDALONE int slrn_case_strcmp (unsigned char *a, register unsigned char *b) { register unsigned char cha, chb; register int diff = a - b; while (1) { cha = UPPER_CASE(b[diff]); chb = UPPER_CASE(*b); if (cha != chb) { return (int) cha - (int) chb; } else if (chb == 0) break; b++; } return 0; } #endif static int unpack_as_shell_archive (FILE *fp, char *buf, int size) { #if SLRN_HAS_PIPING FILE *pp; size--; pp = SLRN_POPEN ("/bin/sh", "w"); if (pp == NULL) { SLRN_ERROR ("Unable to open /bin/sh\n"); return -1; } while (fgets (buf, size, fp) != NULL) { fputs (buf, pp); if (!strcmp (buf, "exit 0\n")) break; } if (-1 == SLRN_PCLOSE (pp)) { SLRN_ERROR ("Error encountered while processing shell archive.\n"); return -1; } return 0; #else slrn_error ("Piping not implemented on this system."); return -1; #endif /* HAS_PIPING */ } static FILE *open_output_file (char *name, char *type) { FILE *fp; fp = fopen (name, "wb"); if (fp == NULL) { SLRN_ERROR_1("Unable to create %s\n", name); return NULL; } SLRN_MESSAGE_2("creating %s (%s)\n", name, type); return fp; } static unsigned char Base64_Table [256]; static void initialize_base64 (void) { int i; for (i = 0; i < 256; i++) Base64_Table[i] = 0xFF; for (i = 'A'; i <= 'Z'; i++) Base64_Table[i] = (unsigned char) (i - 'A'); for (i = 'a'; i <= 'z'; i++) Base64_Table[i] = 26 + (unsigned char) (i - 'a'); for (i = '0'; i <= '9'; i++) Base64_Table[i] = 52 + (unsigned char) (i - '0'); Base64_Table['+'] = 62; Base64_Table['/'] = 63; } /* returns 0 if entire line was decoded, or 1if line appears to be padded, or * -1 if line looks bad. */ /* Calling routine guarantees at least 4 characters in line and multiple of 4 */ static int base64_decode_line (char *line, FILE *fp) { unsigned char ch; unsigned char ch1; unsigned char bytes[3]; unsigned char *p; p = (unsigned char *) line; /* Perform simple syntax check. The loop following this one * assumes this. */ while ((ch = *p++) != 0) { if (Base64_Table[ch] == 0xFF) return -1; ch = *p++; if (Base64_Table[ch] == 0xFF) return -1; ch = *p++; if ((Base64_Table[ch] == 0xFF) && (ch != '=')) { return -1; } ch = *p++; if ((Base64_Table[ch] == 0xFF) && (ch != '=')) { return -1; } } while ((ch = (unsigned char) *line++) != 0) { ch1 = Base64_Table[ch]; bytes[0] = ch1 << 2; ch = *line++; ch1 = Base64_Table[ch]; bytes[0] |= ch1 >> 4; bytes[1] = ch1 << 4; ch = *line++; ch1 = Base64_Table[ch]; if (ch1 == 0xFF) { fwrite (bytes, 1, 1, fp); return 1; } bytes[1] |= ch1 >> 2; bytes[2] = ch1 << 6; ch = *line++; ch1 = Base64_Table[ch]; if (ch1 == 0xFF) { fwrite (bytes, 1, 2, fp); return 1; } bytes[2] |= ch1; fwrite (bytes, 1, 3, fp); } return 0; } static char Base64_Filename[256]; static int Base64_Unknown_Number; /* Returns 1 if padded line which indicates end of encoded file, 0 if * decoded something or -1 if nothing. */ static int decode_base64 (FILE *fp, FILE *fpout, char *line, unsigned int buflen) { int decoding = 0; unsigned int len; int ret; while (NULL != fgets (line, buflen, fp)) { if (Base64_Table[(unsigned char) *line] == 0xFF) { if (decoding) return 0; else return -1; } len = strlen (line); if (len) { if (line [len - 1] == '\n') { line[len - 1] = 0; len--; } } if ((len % 4) != 0) { if (decoding) return 0; else return -1; } ret = base64_decode_line (line, fpout); if (ret) { if ((ret == -1) && decoding) ret = 0; return ret; } decoding = 1; } return 0; } static char *skip_whitespace (char *p) { while ((*p == ' ') || (*p == '\t') || (*p == '\n')) p++; return p; } static char *skip_header_string (char *p, char *name) { unsigned int len; p = skip_whitespace (p); len = strlen (name); if (0 != slrn_case_strncmp ((unsigned char *)p, (unsigned char *)name, len)) return NULL; return p + len; } static char *skip_beyond_name_eqs (char *p, char *name) { int ch; unsigned int len; len = strlen (name); while (1) { p = skip_whitespace (p); if (*p == 0) return NULL; if (0 != slrn_case_strncmp ((unsigned char *) p, (unsigned char *) name, len)) { while (((ch = *p) != 0) && (ch != ' ') && (ch != '\t') && (ch != '\n')) p++; continue; } p = skip_whitespace (p + len); if (*p != '=') continue; p = skip_whitespace (p + 1); if (*p == 0) return NULL; return p; } } static int parse_name_eqs_int (char *p, char *name, int *val) { int ch; int ival; p = skip_beyond_name_eqs (p, name); if (p == NULL) return -1; ival = 0; while (((ch = *p) != 0) && isdigit(ch)) { ival = ival * 10 + (ch - '0'); p++; } *val = ival; return 0; } static int parse_name_eqs_string (char *p, char *name, char *str, unsigned int len) { char ch; char *pmax; int quote = 0; p = skip_beyond_name_eqs (p, name); if (p == NULL) return -1; if (*p == '"') { quote = 1; p++; } pmax = (p + len) - 1; while ((p < pmax) && ((ch = *p) != '"') && (ch != '\n') && (ch != 0)) { if ((quote == 0) && ((ch == ' ') || (ch == '\t'))) break; *str++ = ch; p++; } *str = 0; return 0; } static void parse_content_disposition (char *p) { (void) parse_name_eqs_string (p, "filename", Base64_Filename, sizeof (Base64_Filename)); } static int is_encoding_base64 (char *p) { p = skip_whitespace (p); if (slrn_case_strncmp ((unsigned char *)p, (unsigned char *)"base64", 6)) return 0; return 1; } /* Looking for message/partial; id="bla-bla"; number=10; total=20 * The following is from RFC 1522. It also illustrates what is wrong with * MIME: * * Three parameters must be specified in the Content-Type field of type * message/partial: The first, "id", is a unique identifier, as close to * a world-unique identifier as possible, to be used to match the parts * together. (In general, the identifier is essentially a message-id; * if placed in double quotes, it can be any message-id, in accordance * with the BNF for "parameter" given earlier in this specification.) * The second, "number", an integer, is the part number, which indicates * where this part fits into the sequence of fragments. The third, * "total", another integer, is the total number of parts. This third * subfield is required on the final part, and is optional (though * encouraged) on the earlier parts. Note also that these parameters * may be given in any order. * * Thus, part 2 of a 3-part message may have either of the following * header fields: * * Content-Type: Message/Partial; * number=2; total=3; * id="oc=jpbe0M2Yt4s@thumper.bellcore.com" * * Content-Type: Message/Partial; * id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; * number=2 * * But part 3 MUST specify the total number of parts: * * Content-Type: Message/Partial; * number=3; total=3; * id="oc=jpbe0M2Yt4s@thumper.bellcore.com" * * Note that part numbering begins with 1, not 0. */ typedef struct { int number; /* >= 1 */ int total; /* not required!! */ char id[256]; /* required */ } Mime_Message_Partial_Type; static int parse_content_type (char *p, Mime_Message_Partial_Type *mpt) { #if 1 (void) parse_name_eqs_string (p, "name", Base64_Filename, sizeof (Base64_Filename)); #endif if ((NULL == (p = skip_header_string (p, "message"))) || (NULL == (p = skip_header_string (p, "/"))) || (NULL == (p = skip_header_string (p, "partial"))) || (NULL == (p = skip_header_string (p, ";")))) return -1; if (-1 == parse_name_eqs_int (p, "number", &mpt->number)) return -1; if (-1 == parse_name_eqs_int (p, "total", &mpt->total)) mpt->total = 0; /* not reqired on all parts */ if (-1 == parse_name_eqs_string (p, "id", mpt->id, sizeof (mpt->id))) { *mpt->id = 0; return -1; } return 0; } static void read_in_whole_header_line (char *line, FILE *fp) { unsigned int len; unsigned int space; int c; /* We have already read in one line. Prepare to read in continuation lines */ len = strlen (line); space = MAX_ARTICLE_LINE_LEN - len; while (space) { c = getc (fp); if (c == EOF) return; if ((c != ' ') && (c != '\t')) { ungetc (c, fp); return; } line += len; if (NULL == fgets (line, space, fp)) return; len = strlen (line); space -= len; } } static int check_and_decode_base64 (FILE *fp) { char *p; int found_base64_signature = 0; FILE *fpout = NULL; char line[MAX_ARTICLE_LINE_LEN]; Mime_Message_Partial_Type *current_pmt, pmt_buf; int part = 1; initialize_base64 (); current_pmt = NULL; while (NULL != fgets (line, sizeof (line), fp)) { try_again: if (*line == '\n') { if (found_base64_signature) { int ret; if (*Base64_Filename == 0) { sprintf (Base64_Filename, "unknown-64.%d", Base64_Unknown_Number); Base64_Unknown_Number++; } if (fpout == NULL) fpout = open_output_file (Base64_Filename, "base64"); if (fpout == NULL) return -1; ret = decode_base64 (fp, fpout, line, sizeof (line)); /* decode_base64 performs fgets. So if nothing was decoded * skip fgets at top of loop */ if (ret == -1) goto try_again; if ((current_pmt == NULL) || (current_pmt->number == current_pmt->total) || (part != current_pmt->number) || (ret == 1)) { fclose (fpout); fpout = NULL; found_base64_signature = 0; current_pmt = NULL; part = 0; *Base64_Filename = 0; } part++; } continue; } /* Look for 'Content-' */ if (slrn_case_strncmp ((unsigned char *) "Content-", (unsigned char *) line, 8)) continue; read_in_whole_header_line (line, fp); p = line + 8; if (0 == slrn_case_strncmp ((unsigned char *) p, (unsigned char *) "Transfer-Encoding: ", 19)) { if (is_encoding_base64 (p + 19)) { if (found_base64_signature) { if (fpout != NULL) { fclose (fpout); fpout = NULL; current_pmt = NULL; *Base64_Filename = 0; part = 1; } } found_base64_signature = 1; } continue; } if (0 == slrn_case_strncmp ((unsigned char *)p, (unsigned char *)"Disposition: ", 13)) { parse_content_disposition (p + 13); continue; } if (0 == slrn_case_strncmp ((unsigned char *)p, (unsigned char *)"Type: ", 6)) { Mime_Message_Partial_Type pmt; if (-1 == parse_content_type (p + 6, &pmt)) { /* current_pmt = NULL; */ continue; } if ((current_pmt == NULL) || (pmt.number == 1) || (part != pmt.number) || strcmp (current_pmt->id, pmt.id)) { if (fpout != NULL) { fclose (fpout); fpout = NULL; *Base64_Filename = 0; found_base64_signature = 0; part = 1; } } current_pmt = &pmt_buf; memcpy ((char *) current_pmt, (char *) &pmt, sizeof (Mime_Message_Partial_Type)); continue; } } if (fpout != NULL) fclose (fpout); return 0; } static int parse_uuencode_begin_line (unsigned char *b, char **file, int *mode) { int m = 0; unsigned char *bmax; while (*b == ' ') b++; if (0 == isdigit (*b)) return -1; while (isdigit (*b)) { m = m * 8 + (*b - '0'); b++; } if (*b != ' ') return -1; while (*b == ' ') b++; if (*b < ' ') return -1; bmax = b + strlen ((char *) b); while (*bmax <= ' ') *bmax-- = 0; *mode = m; *file = (char *) b; return 0; } static int check_and_uudecode (FILE *fp) { FILE *outfp = NULL; char buf[MAX_ARTICLE_LINE_LEN]; while (NULL != fgets (buf, sizeof (buf), fp)) { char *file; int mode = 0; /* We are looking for 'begin ddd filename' */ if (strncmp (buf, "begin ", 6)) { if ((buf[0] == '#') && (buf[1] == '!')) { char *binsh = buf + 2; if (*binsh == ' ') binsh++; if (!strcmp (binsh, "/bin/sh\n")) { unpack_as_shell_archive (fp, buf, sizeof (buf)); } } continue; } if (-1 == parse_uuencode_begin_line ((unsigned char *)buf + 6, &file, &mode)) continue; outfp = open_output_file (file, "uuencoded"); if (outfp == NULL) { return -1; } chmod (file, mode); /* Now read parts of the file in. */ while (fgets (buf, sizeof(buf) - 1, fp) != NULL) { unsigned int len; unsigned int buflen; unsigned char out[60], *outp, *outmax, *b, *bmax; if (((*buf & 0x3F) == ' ') && (*(buf + 1) == '\n')) { /* we may be on the last line before the end. Lets check */ if (NULL == fgets (buf, sizeof(buf) - 1, fp)) { SLRN_ERROR ("Unexpected end of file.\n"); return -1; } if (!strcmp (buf, "end\n")) { slrn_fclose (outfp); outfp = NULL; break; } } /* Now perform some sanity checking */ len = *buf; if ((len <= ' ') || ((buflen = strlen (buf)) > 62) || (buflen < 2) || ((buflen - 2) % 4) != 0) { if (strncmp (buf, "begin ", 6) || (-1 == parse_uuencode_begin_line ((unsigned char *)buf + 6, &file, &mode))) continue; if (outfp == NULL) slrn_fclose (outfp); outfp = open_output_file (file, "uuencoded"); if (outfp == NULL) { return -1; } continue; } buflen -= 2; len -= ' '; /* In general, I cannot make the test: * @ if ((3 * buflen) != (len * 4)) continue; * The problem is that the last line may be padded. Instead, do * it just for normal length lines and handle padding for others. */ if (*buf == 'M') { if ((3 * buflen) != (len * 4)) continue; } else { if (buflen != ((len + 2) / 3) * 4) continue; } b = (unsigned char *) buf + 1; bmax = b + buflen; while (b < bmax) { *b = (*b - 32) & 0x3F; *(b + 1) = (*(b + 1) - 32) & 0x3F; *(b + 2) = (*(b + 2) - 32) & 0x3F; *(b + 3) = (*(b + 3) - 32) & 0x3F; b += 4; } b = (unsigned char *) buf + 1; outp = out; outmax = outp + len; while (outp < outmax) { register unsigned char b1, b2; b1 = *(b + 1); b2 = *(b + 2); *outp++ = (*b << 2) | (b1 >> 4); if (outp < outmax) { *outp++ = (b1 << 4) | (b2 >> 2); if (outp < outmax) *outp++ = (b2 << 6) | *(b + 3); } b += 4; } if (len != fwrite ((char *) out, 1, len, outfp)) { SLRN_ERROR ("write to file failed.\n"); slrn_fclose (outfp); return -1; } } /* end of part reading */ /* back to looking for something else */ } if (outfp != NULL) { slrn_fclose (outfp); outfp = NULL; } return 0; } int slrn_uudecode_file (char *file, char *newdir, int base64_only) { FILE *fp; #ifdef __os2__ char olddir_buf[256]; #endif int ret; if (newdir == NULL) { #ifndef STANDALONE char newdir_buf[256]; #ifdef __os2__ if (getwd(olddir_buf) == 0) { SLRN_ERROR_1 ("Unable to save old directory name: %s\n", olddir_buf); return -1; } #endif if (Slrn_Decode_Directory == NULL) Slrn_Decode_Directory = "News"; newdir = Slrn_Decode_Directory; slrn_make_home_dirname (newdir, newdir_buf); newdir = newdir_buf; #endif } if ((newdir != NULL) && (-1 == chdir(newdir))) { SLRN_ERROR_1 ("Unable to chdir to %s.\n", newdir); return -1; } if (file == NULL) fp = stdin; else fp = fopen (file, "r"); if (fp == NULL) { SLRN_ERROR_1("Unable to open %s.\n", file); return -1; } if (base64_only == 0) { ret = check_and_uudecode (fp); if ((ret == 0) && (fp != stdin)) { rewind (fp); ret = check_and_decode_base64 (fp); } } else ret = check_and_decode_base64 (fp); if (fp != stdin) slrn_fclose (fp); #ifdef __os2__ if (-1 == chdir(olddir_buf)) { SLRN_ERROR_1 ("Unable to chdir back to %s.\n", olddir_buf); return -1; } #endif #ifdef STANDALONE SLRN_MESSAGE ("No more files found.\n"); #endif return ret; } #ifdef STANDALONE static void usage (void) { fprintf (stderr, "Usage: uudecode [-64] [filename ...]\n"); } int main (int argc, char **argv) { int i; int base64_only = 0; if (argc > 1) { if (!strcmp ("--help", argv[1])) { usage (); return 1; } if (!strcmp ("-64", argv[1])) { base64_only = 1; argc--; argv++; } } if (argc <= 1) { if (!isatty (fileno(stdin))) { return slrn_uudecode_file (NULL, NULL, base64_only); } usage (); return 1; } for (i = 1; i < argc; i++) { slrn_uudecode_file (argv[i], NULL, base64_only); } return 0; } #endif
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.