home *** CD-ROM | disk | FTP | other *** search
/ Amiga Format CD 28 / amigaformatcd28.iso / -seriously_amiga- / archivers / mpackppc-wos / src / uudecode.c < prev    next >
C/C++ Source or Header  |  1998-04-27  |  29KB  |  852 lines

  1. /* (C) Copyright 1993,1994 by Carnegie Mellon University
  2.  * All Rights Reserved.
  3.  *
  4.  * Permission to use, copy, modify, distribute, and sell this software
  5.  * and its documentation for any purpose is hereby granted without
  6.  * fee, provided that the above copyright notice appear in all copies
  7.  * and that both that copyright notice and this permission notice
  8.  * appear in supporting documentation, and that the name of Carnegie
  9.  * Mellon University not be used in advertising or publicity
  10.  * pertaining to distribution of the software without specific,
  11.  * written prior permission.  Carnegie Mellon University makes no
  12.  * representations about the suitability of this software for any
  13.  * purpose.  It is provided "as is" without express or implied
  14.  * warranty.
  15.  *
  16.  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
  17.  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  18.  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
  19.  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  20.  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  21.  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
  22.  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  23.  * SOFTWARE.
  24.  */
  25. #include <stdio.h>
  26. #include <ctype.h>
  27. #include <string.h>
  28. #include "xmalloc.h"
  29. #include "common.h"
  30. #include "part.h"
  31.  
  32. extern char *os_idtodir();
  33. extern FILE *os_newtypedfile();
  34.  
  35. static FILE *startDescFile();
  36.  
  37.  
  38. /* Length of a normal uuencoded line, including newline */
  39. #define UULENGTH 62
  40.  
  41. /*
  42.  * Table of valid boundary characters
  43.  *
  44.  * XXX: Old versions of Mark Crispin's c-client library
  45.  * generate boundaries which contain the syntactically
  46.  * illegal character '#'.  It is marked in this table with
  47.  * a 2 in case we want to use this table in the future to
  48.  * complain about bad syntax.
  49.  *
  50.  */
  51. static char bchar[256] = {
  52.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  53.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  54.      1, 0, 0, 2,  0, 0, 0, 1,  1, 1, 0, 1,  1, 1, 1, 1,
  55.      1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 0,  0, 1, 0, 1,
  56.      0, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,
  57.      1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 0,  0, 0, 0, 1,
  58.      0, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 1,
  59.      1, 1, 1, 1,  1, 1, 1, 1,  1, 1, 1, 0,  0, 0, 0, 0,
  60.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  61.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  62.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  63.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  64.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  65.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  66.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  67.      0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,
  68. };
  69.  
  70. /*
  71.  * Read an input file, looking for data in split-uuencode format
  72.  */
  73. handleUuencode(struct part *inpart, char *subject, int extractText)
  74. {
  75.     char *fname = 0, *tmpfname;
  76.     int part, nparts;
  77.     int tmppart, tmpnparts;
  78.     char buf[1024], buf2[1024];
  79.     char fnamebuf[80];
  80.     char *boundary_end, *p;
  81.     int wantdescfile = 0;
  82.     FILE *descfile = 0;
  83.  
  84.     /* Scan "Subject:" header for filename/part information */
  85.     if (parseSubject(subject, &fname, &part, &nparts) != 0) {
  86.         part = -1;
  87.     }
  88.     if (part == 0) {
  89.         return saveUuFile(inpart, fname, part, nparts, (char *)0);
  90.     }
  91.     if (part == 1) {
  92.         wantdescfile = 1;
  93.     }
  94.  
  95.     /* Scan body for interesting lines */
  96.     while (part_gets(buf, sizeof(buf), inpart)) {
  97.         /* Uuencode "begin" line */
  98.         if (!strncmp(buf, "begin ", 6) &&
  99.             isdigit(buf[6]) && isdigit(buf[7]) && isdigit(buf[8]) &&
  100.             buf[9] == ' ') {
  101.             if (part == -1) {
  102.                 /*
  103.                  * We have no part N of M information.  Perhaps it is
  104.                  * a single-part uuencoded file.
  105.                  */
  106.                 return saveUuFile(inpart, (char *)0, 1, 0, buf);
  107.             }
  108.             else {
  109.                 if (descfile) fclose(descfile);
  110.                 return saveUuFile(inpart, fname, part, nparts, buf);
  111.             }
  112.         }
  113.         else if (!strncmp(buf, "section ", 8) && isdigit(buf[8])) {
  114.             tmppart = 0;
  115.             for (p = buf+8; isdigit(*p); p++) tmppart = tmppart*10 + *p - '0';
  116.             if (tmppart == 0) continue;
  117.             if (strncmp(p, " of ", 4) == 0) {
  118.                 /*
  119.                  * "section N of ... of file F ..."
  120.                  */
  121.                 for (p += 4; *p && strncmp(p, " of file ", 9) != 0; p++);
  122.                 if (!*p) continue;
  123.                 p += 9;
  124.                 tmpfname = p;
  125.                 p = strchr(p, ' ');
  126.                 if (!p) continue;
  127.                 *p = '\0';
  128.                 if (descfile) fclose(descfile);
  129.                 return saveUuFile(inpart, tmpfname, tmppart, 0, (char *)0);
  130.             }
  131.             else if (*p == '/' && isdigit(p[1])) {
  132.                 /*
  133.                  * "section N/M   file F ..."
  134.                  */
  135.                 tmpnparts = 0;
  136.                 for (p++; isdigit(*p); p++) {
  137.                     tmpnparts = tmpnparts*10 + *p - '0';
  138.                 }
  139.                 while (*p && isspace(*p)) p++;
  140.                 if (tmppart > tmpnparts || strncmp(p, "file ", 5) != 0) {
  141.                     continue;
  142.                 }
  143.                 tmpfname = p+5;
  144.                 p = strchr(tmpfname, ' ');
  145.                 if (!p) continue;
  146.                 *p = '\0';
  147.                 if (descfile) fclose(descfile);
  148.                 return saveUuFile(inpart, tmpfname, tmppart, tmpnparts,
  149.                                   (char *)0);
  150.             }
  151.         }
  152.         else if (!strncmp(buf, "POST V", 6)) {
  153.             /*
  154.              * "POST Vd.d.d F (Part N/M)"
  155.              */
  156.             p = strchr(buf+6, ' ');
  157.             if (!p) continue;
  158.             tmpfname = p+1;
  159.             p = strchr(tmpfname, ' ');
  160.             if (!p || strncmp(p, " (Part ", 7) != 0) continue;
  161.             *p = '\0';
  162.             p += 7;
  163.             tmppart = 0;
  164.             while (isdigit(*p)) tmppart = tmppart*10 + *p++ - '0';
  165.             if (tmppart == 0 || *p++ != '/') continue;
  166.             tmpnparts = 0;
  167.             while (isdigit(*p)) tmpnparts = tmpnparts*10 + *p++ - '0';
  168.             if (tmppart > tmpnparts || *p != ')') continue;
  169.             if (descfile) fclose(descfile);
  170.             return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0);
  171.         }
  172.         else if (!strncmp(buf, "File: ", 6)) {
  173.             /*
  174.              * "File: F -- part N of M -- ...
  175.              */
  176.             tmpfname = buf+6;
  177.             p = strchr(tmpfname, ' ');
  178.             if (!p || strncmp(p, " -- part ", 9) != 0) continue;
  179.             *p = '\0';
  180.             p += 9;
  181.             tmppart = 0;
  182.             while (isdigit(*p)) tmppart = tmppart*10 + *p++ - '0';
  183.             if (tmppart == 0 || strncmp(p, " of ", 4) != 0) continue;
  184.             p += 4;
  185.             tmpnparts = 0;
  186.             while (isdigit(*p)) tmpnparts = tmpnparts*10 + *p++ - '0';
  187.             if (tmppart > tmpnparts || strncmp(p, " -- ", 4) != 0) continue;
  188.             if (descfile) fclose(descfile);
  189.             return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0);
  190.         }
  191.         else if (!strncmp(buf, "[Section: ", 10)) {
  192.             /*
  193.              * "[Section: N/M  File: F ..."
  194.              */
  195.             tmppart = 0;
  196.             for (p = buf+10; isdigit(*p); p++) tmppart = tmppart*10 + *p - '0';
  197.             if (tmppart == 0) continue;
  198.             tmpnparts = 0;
  199.             for (p++; isdigit(*p); p++) {
  200.                 tmpnparts = tmpnparts*10 + *p - '0';
  201.             }
  202.             while (*p && isspace(*p)) p++;
  203.             if (tmppart > tmpnparts || strncmp(p, "File: ", 6) != 0) {
  204.                 continue;
  205.             }
  206.             tmpfname = p+6;
  207.             p = strchr(tmpfname, ' ');
  208.             if (!p) continue;
  209.             *p = '\0';
  210.             if (descfile) fclose(descfile);
  211.             return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0);
  212.         }
  213.         else if (*buf == '[') {
  214.             /*
  215.              * "[F ... - part N of M]"
  216.              * (usual BinHex practice)
  217.              */
  218.             tmpfname = buf+1;
  219.             p = strchr(tmpfname, ' ');
  220.             if (!p) continue;
  221.             *p++ = '\0';
  222.             while (p && strncmp(p, "- part ", 7) != 0) {
  223.                 p = strchr(p+1, '-');
  224.             }
  225.             if (!p) continue;
  226.             p += 7;
  227.             tmppart = 0;
  228.             while (isdigit(*p)) tmppart = tmppart*10 + *p++ - '0';
  229.             if (tmppart == 0 || strncmp(p, " of ", 4) != 0) continue;
  230.             p += 4;
  231.             tmpnparts = 0;
  232.             while (isdigit(*p)) tmpnparts = tmpnparts*10 + *p++ - '0';
  233.             if (tmppart > tmpnparts || *p != ']') continue;
  234.             if (descfile) fclose(descfile);
  235.             return saveUuFile(inpart, tmpfname, tmppart, tmpnparts, (char *)0);
  236.         }
  237.         else if (fname && part > 0 && nparts > 0 && part <= nparts &&
  238.                  (!strncmp(buf, "BEGIN", 5) ||
  239.                   !strncmp(buf, "--- BEGIN ---", 12) ||
  240.                   (buf[0] == 'M' && strlen(buf) == UULENGTH))) {
  241.             /*
  242.              * Found the start of a section of uuencoded data
  243.              * and have the part N of M information.
  244.              */
  245.             if (descfile) fclose(descfile);
  246.             return saveUuFile(inpart, fname, part, nparts, buf);
  247.         }
  248.         else if (!cistrncmp(buf, "x-file-name: ", 13)) {
  249.             for (p = buf + 13; *p && !isspace(*p); p++);
  250.             *p = '\0';
  251.             strncpy(fnamebuf, buf+13, sizeof(fnamebuf)-1);
  252.             fnamebuf[sizeof(fnamebuf)-1] = '\0';
  253.             fname = fnamebuf;
  254.             continue;
  255.         }
  256.         else if (!cistrncmp(buf, "x-part: ", 8)) {
  257.             tmppart = atoi(buf+8);
  258.             if (tmppart > 0) part = tmppart;
  259.             continue;
  260.         }
  261.         else if (!cistrncmp(buf, "x-part-total: ", 14)) {
  262.             tmpnparts = atoi(buf+14);
  263.             if (tmpnparts > 0) nparts = tmpnparts;
  264.             continue;
  265.         }
  266.         else if (part == 1 && fname && !descfile &&
  267.                  !cistrncmp(buf, "x-file-desc: ", 13)) {
  268.             if (descfile = startDescFile(fname)) {
  269.                 fputs(buf+13, descfile);
  270.                 if (descfile) fclose(descfile);
  271.                 descfile = 0;
  272.             }
  273.             continue;
  274.         }
  275.         else if (!strcmp(buf,
  276.                          "(This file must be converted with BinHex 4.0)\n")) {
  277.             if (descfile) fclose(descfile);
  278.             return os_binhex(inpart, 1, 1);
  279.         }
  280.         else if (!cistrncmp(buf, "content-", 8)) {
  281.             /*
  282.              * HEURISTIC: If we see something that looks like a content-*
  283.              * header, push it back and call the message parser.
  284.              */
  285.             p = buf+8;
  286.             /* Check to see if header's field-name is syntactically valid */
  287.             while (*p) {
  288.                 if (*p == ':' || *p <= ' ' || *p >= '\177') break;
  289.                 p++;
  290.             }
  291.             if (*p == ':') {
  292.                 part_ungets(buf, inpart);
  293.                 if (descfile) fclose(descfile);
  294.                 return handleMessage(inpart, "text/plain", 0, extractText);
  295.             }
  296.         }
  297.         if (buf[0] == '-' && buf[1] == '-') {
  298.             /*
  299.              * Heuristic: If we see something that looks like a
  300.              * multipart boundary, followed by something that looks
  301.              * like a header, push them back and parse as a multipart.
  302.              */
  303.             p = buf+2;
  304.             while (*p) {
  305.                 if (!bchar[(unsigned char)*p]) break;
  306.                 p++;
  307.             }
  308.             if (*p != '\n') {
  309.                 /*
  310.                  * We found an invalid boundary character.
  311.                  * Move 'p' such that it will fail all subsequent checks.
  312.                  */
  313.                 p = buf + 2;
  314.             }
  315.             /* Back up to ignore trailing whitespace */
  316.             while (p > buf+2 && p[-1] == ' ') p--;
  317.  
  318.             /*
  319.              * Check that boundary is within legal size limits
  320.              * If so, peek at next line
  321.              */
  322.             if (p - buf > 2 && p - buf <= 72 &&
  323.                 part_gets(buf2, sizeof(buf2), inpart)) {
  324.                 boundary_end = p;
  325.                 p = buf2;
  326.                 /*
  327.                  * Check to see if a syntactically valid header follows
  328.                  * what looks to be a boundary.
  329.                  *
  330.                  * XXX: Unfortunately, we can't check for "Content-";
  331.                  * it is syntactically valid to have a body-part
  332.                  * header that doesn't start with that and ZMail
  333.                  * takes advantage of that.  If this heuristic starts
  334.                  * causing problems, we could keep looking ahead until
  335.                  * we find a "Content-" header or find something that's
  336.                  * not a header.
  337.                  */
  338.                 while (*p) {
  339.                     if (*p == ':' || *p <= ' ' || *p >= '\177') break;
  340.                     p++;
  341.                 }
  342.                 
  343.                 /* Push back the lookahead line */
  344.                 part_ungets(buf2, inpart);
  345.  
  346.                 if (p > buf2 && *p == ':') {
  347.                     /* Push back the boundary */
  348.                     part_ungets(buf, inpart);
  349.  
  350.                     /*
  351.                      * Generate and push back a header to get us into
  352.                      * the multipart parser.
  353.                      */
  354.                     *boundary_end = '\0';
  355.                     sprintf(buf2,
  356.                          "Content-type: multipart/mixed; boundary=\"%s\"\n\n",
  357.                             buf+2);
  358.                     part_ungets(buf2, inpart);
  359.                     
  360.                     if (descfile) fclose(descfile);
  361.                     return handleMessage(inpart, "text/plain", 0, extractText);
  362.                 }
  363.             }
  364.         }
  365.  
  366.         /*
  367.          * Save useful-looking text that is before a "part 1 of N"
  368.          * in a description file.
  369.          */
  370.         if (wantdescfile && !descfile) {
  371.             for (p = buf; *p && isspace(*p); p++);
  372.             if (*p) {
  373.                 if (!cistrncmp(p, "x-", 2)) {
  374.                     /*
  375.                      * Check for "X-foobar:"
  376.                      * If so, there probably will be a "X-File-Desc:" line
  377.                      * later, so ignore this line.
  378.                      */
  379.                     while (*p != ':' && *p > ' ' && *p < '\177') p++;
  380.                     if (*p == ':') continue;
  381.                 }
  382.                 if (!descEnd(buf) && (descfile = startDescFile(fname))) {
  383.                     fputs(buf, descfile);
  384.                 }
  385.                 wantdescfile = 0;
  386.             }
  387.         }
  388.         else if (descfile) {
  389.             if (descEnd(buf)) {
  390.                 if (descfile) fclose(descfile);
  391.                 descfile = 0;
  392.             }
  393.             else {
  394.                 fputs(buf, descfile);
  395.             }
  396.         }
  397.     }   
  398.  
  399.     if (descfile) fclose(descfile);
  400.     return 0;
  401. }
  402.  
  403. /*
  404.  * Handle a split-uuencode part
  405.  * If nparts is 0, then look for an "end" line to detect the last part.
  406.  * If fname is null, then we are attempting to decode a single-part message.
  407.  * If firstline is non-null, it is written as the first line of the saved part
  408.  */
  409. int
  410. saveUuFile(struct part *inpart, char *fname, int part, int nparts, char *firstline)
  411. {
  412.     char buf[1024];
  413.     char *dir;
  414.     FILE *partfile;
  415.  
  416.     if (fname) {
  417.         sprintf(buf, "Saving part %d ", part);
  418.         if (nparts) sprintf(buf+strlen(buf), "of %d ", nparts);
  419.         strcat(buf, fname);
  420.         chat(buf);
  421.     }
  422.     else fname = "unknown";
  423.  
  424.     /* Create directory to store parts and copy this part there. */
  425.     dir = os_idtodir(fname);
  426.     if (!dir) return 1;
  427.     sprintf(buf, "%s%d", dir, part);
  428.     partfile = fopen(buf, "w");
  429.         if (!partfile) {
  430.         os_perror(buf);
  431.         return 1;
  432.     }
  433.     if (firstline) fputs(firstline, partfile);
  434.     while (part_gets(buf, sizeof(buf), inpart)) {
  435.         fputs(buf, partfile);
  436.         if (nparts == 0 && strcmp(buf, "end\n") == 0) {
  437.             /* This is the last part. Remember the fact */
  438.             nparts = part;
  439.             if (partfile) fclose(partfile);
  440.             sprintf(buf, "%sCT", dir);
  441.             partfile = fopen(buf, "w");
  442.             if (!partfile) {
  443.                 os_perror(buf);
  444.             }
  445.             else {
  446.                 fprintf(partfile, "%d\n", nparts);
  447.             }
  448.             break;
  449.         }
  450.     }
  451.     if (partfile) fclose(partfile);
  452.  
  453.     /* Retrieve any previously saved number of the last part */
  454.     if (nparts == 0) {
  455.         sprintf(buf, "%sCT", dir);
  456.         if (partfile = fopen(buf, "r")) {
  457.             if (fgets(buf, sizeof(buf), partfile)) {
  458.                 nparts = atoi(buf);
  459.                 if (nparts < 0) nparts = 0;
  460.             }
  461.             if (partfile) fclose(partfile);
  462.         }
  463.     }
  464.  
  465.     if (nparts == 0) return 0;
  466.  
  467.     /* Check to see if we have all parts.  Start from the highest numbers
  468.      * as we are more likely not to have them.
  469.      */
  470.     for (part = nparts; part; part--) {
  471.         sprintf(buf, "%s%d", dir, part);
  472.         partfile = fopen(buf, "r");
  473.         if (partfile) {
  474.             fclose(partfile);
  475.         }
  476.         else {
  477.             return 0;
  478.         }
  479.     }
  480.  
  481.     return uudecodefiles(dir, nparts);
  482. }
  483.  
  484. /*
  485.  * Parse a Subject: header, looking for clues with which to decode
  486.  * split-uuencoded data.
  487.  */
  488. int
  489. parseSubject(char *subject, char **fnamep, int *partp, int *npartsp)
  490. {
  491.     char *scan, *bak, *start;
  492.     int part = -1, nparts = 0, hasdot = 0;
  493.  
  494.     /* No subject header */
  495.     if (!subject) return 1;
  496.  
  497.     /* Skip leading whitespace and other garbage */
  498.     scan = subject;
  499.     while (*scan == ' ' || *scan == '\t' || *scan == '-') scan++;
  500.     if (!cistrncmp(scan, "repost", 6)) {
  501.         for (scan += 6; *scan == ' ' || *scan == '\t'
  502.              || *scan == ':' || *scan == '-'; scan++);
  503.     }
  504.  
  505.     /* Replies aren't usually data */
  506.     if (!cistrncmp(scan, "re:", 3)) return 1;
  507.  
  508.     /* Get filename */
  509.  
  510.     /* Grab the first filename-like string.  Explicitly ignore strings with
  511.      * prefix "v<digit>" ending in ":", since that is a popular volume/issue
  512.      * representation syntax
  513.      */
  514.     do {
  515.         while (*scan != '\n' && isprint(*scan)
  516.                && !isalnum(*scan) && *scan != '_') ++scan;
  517.         *fnamep = start = scan;
  518.         while (isalnum(*scan) || *scan == '-' || *scan == '+' || *scan == '&'
  519.                || *scan == '_' || *scan == '.') {
  520.             if (*scan++ == '.') hasdot = 1;
  521.         }
  522.         if (!*scan || *scan == '\n') return 1;
  523.     } while (start == scan
  524.              || (start[0] == 'v' && isdigit(start[1]) && *scan == ':'));
  525.     *scan++ = '\0';
  526.     
  527.     /* Try looking for a filename with a "." in it later in the subject line.
  528.      * Exclude <digit>.<digit>, since that is usually a version number.
  529.      */
  530.     if (!hasdot) {
  531.         while (*(start = scan) != '\0' && *scan != '\n') {
  532.             while (isspace(*start)) ++start;
  533.             for (scan = start; isalnum(*scan) || *scan == '-' || *scan == '+'
  534.                  || *scan == '&' || *scan == '_' || *scan == '.'; ++scan) {
  535.                 if (*scan == '.' && 
  536.                     (!isdigit(scan[-1]) || !isdigit(scan[1]))) {
  537.                     hasdot = 1;
  538.                 }
  539.             }
  540.             if (hasdot && scan > start) {
  541.                 *fnamep = start;
  542.                 *scan++ = '\0';
  543.                 break;
  544.             }
  545.             while (*scan && *scan != '\n' && !isalnum(*scan)) ++scan;
  546.         }
  547.         scan = *fnamep + strlen(*fnamep) + 1;
  548.     }
  549.  
  550.     /* Get part number */
  551.     while (*scan && *scan != '\n') {
  552.         /* skip over versioning */
  553.         if (*scan == 'v' && isdigit(scan[1])) {
  554.             ++scan;
  555.             while (isdigit(*scan)) ++scan;
  556.         }
  557.         /* look for "1/6" or "1 / 6" or "1 of 6" or "1-of-6" or "1o6" */
  558.         if (isdigit(*scan) &&
  559.             (scan[1] == '/'
  560.              || (scan[1] == ' ' && scan[2] == '/')
  561.              || (scan[1] == ' ' && scan[2] == 'o' && scan[3] == 'f')
  562.              || (scan[1] == '-' && scan[2] == 'o' && scan[3] == 'f')
  563.              || (scan[1] == 'o' && isdigit(scan[2])))) {
  564.             while (isdigit(scan[-1])) scan--;
  565.             part = 0;
  566.             while (isdigit(*scan)) {
  567.                 part = part * 10 + *scan++ - '0';
  568.             }
  569.             while (*scan != '\0' && *scan != '\n' && !isdigit(*scan)) scan++;
  570.             if (isdigit(*scan)) {
  571.                 nparts = 0;
  572.                 while (isdigit(*scan)) {
  573.                     nparts = nparts * 10 + *scan++ - '0';
  574.                 }
  575.             }
  576.             break;
  577.         }
  578.  
  579.         /* look for "6 parts" or "part 1" */
  580.         if (!cistrncmp("part", scan, 4)) {
  581.             if (scan[4] == 's') {
  582.                 for (bak = scan; bak >= subject && !isdigit(*bak); bak--);
  583.                 if (bak > subject) {
  584.                     while (bak > subject && isdigit(bak[-1])) bak--;
  585.                     nparts = 0;
  586.                     while (isdigit(*bak)) {
  587.                         nparts = nparts * 10 + *bak++ - '0';
  588.                     }
  589.                 }
  590.             } else {
  591.                 while (*scan && *scan != '\n' && !isdigit(*scan)) scan++;
  592.                 bak = scan - 1;
  593.                 if (isdigit(*scan)) {
  594.                     part = 0;
  595.                     do {
  596.                         part = part * 10 + *scan++ - '0';
  597.                     } while (isdigit(*scan));
  598.                 }
  599.                 scan = bak;
  600.             }
  601.         }
  602.         scan++;
  603.     }
  604.  
  605.     if (nparts == 0 || part == -1 || part > nparts) return 1;
  606.     *partp = part;
  607.     *npartsp = nparts;
  608.     return 0;
  609. }
  610.  
  611. /*
  612.  * Return nonzero if 'line' should mark the end of a part-1 description
  613.  */
  614. int
  615. descEnd(char *line)
  616. {
  617.     return !strncmp(line, "---", 3) ||
  618.         !strncmp(line, "#!", 2) ||
  619.         !cistrncmp(line, "part=", 5) ||
  620.         !cistrncmp(line, "begin", 5);
  621. }
  622.  
  623. /*
  624.  * Open and return a file pointer for a description file for 'fname'.
  625.  * If a description file for 'fname' already exists, or if there is an
  626.  * error, return a null pointer.
  627.  */
  628. static FILE *startDescFile(char *fname)
  629. {
  630.     char buf[1024];
  631.     char *dir;
  632.     FILE *descfile;
  633.  
  634.     /* Create directory to store parts and copy this part there. */
  635.     dir = os_idtodir(fname);
  636.     if (!dir) return 0;
  637.     sprintf(buf, "%s0", dir);
  638.  
  639.     /* See if part 0 already exists, return failure if so */
  640.     descfile = fopen(buf, "r");
  641.     if (descfile) {
  642.         fclose(descfile);
  643.         return 0;
  644.     }
  645.  
  646.     descfile = fopen(buf, "w");
  647.     if (!descfile) {
  648.         os_perror(buf);
  649.         return 0;
  650.     }
  651.     return descfile;
  652. }
  653.  
  654. /*
  655.  * Decode the uuencoded file that is in 'nparts' pieces in 'dir'.
  656.  */
  657. int
  658. uudecodefiles(char *dir, int nparts)
  659. {
  660.     int part;
  661.     enum {st_start, st_inactive, st_decode, st_nextlast, st_last,
  662.             st_binhex} state;
  663.     FILE *infile;
  664.     FILE *outfile = NULL;
  665.     struct part *inpart;
  666.     char buf[1024];
  667.     char lastline[UULENGTH+1];
  668.     char *fname, *p;
  669.     char *contentType = "application/octet-stream";
  670.     int line_length = 0;
  671.  
  672.     /* If a part 0, copy to description filename */
  673.     sprintf(buf, "%s0", dir);
  674.     infile = fopen(buf, "r");
  675.     if (infile) {
  676.         outfile = fopen(TEMPFILENAME, "w");
  677.         if (outfile) {
  678.             while (fgets(buf, sizeof(buf), infile)) {
  679.                 fputs(buf, outfile);
  680.             }
  681.             if (outfile) fclose(outfile);
  682.             outfile = NULL;
  683.         }
  684.         if (infile) fclose(infile);
  685.         sprintf(buf, "%s0", dir);
  686.         remove(buf);
  687.     }
  688.  
  689.     state = st_start;
  690.  
  691.     /* Handle each part in order */
  692.     for (part = 1; part <= nparts; part++) {
  693.         sprintf(buf, "%s%d", dir, part);
  694.         infile = fopen(buf, "r");
  695.         if (!infile) {
  696.             os_perror(buf);
  697.             if (outfile) fclose(outfile);
  698.             remove(TEMPFILENAME);
  699.             return 1;
  700.         }
  701.  
  702.         while (fgets(buf, sizeof(buf), infile)) {
  703.             switch (state) {
  704.             case st_start:      /* Looking for start of uuencoded
  705.                                  *  or binhex'ed file */
  706.                 if (!strcmp(buf,
  707.                         "(This file must be converted with BinHex 4.0)\n")) {
  708.                     state = st_binhex;
  709.                     inpart = part_init(infile);
  710.                     os_binhex(inpart, part, nparts);
  711.                     part_close(inpart);
  712.                     goto endbinhex;
  713.                 }
  714.                 if (strncmp(buf, "begin ", 6)) break;
  715.                 /* skip mode */
  716.                 p = buf + 6;
  717.                 while (*p && !isspace(*p)) p++;
  718.                 while (*p && isspace(*p)) p++;
  719.                 fname = p;
  720.                 while (*p && !isspace(*p)) p++;
  721.                 *p = '\0';
  722.                 if (!*fname) return 1;
  723.  
  724.                 /* Guess the content-type of common filename extensions */
  725.                 if (p = strrchr(fname, '.')) {
  726.                     if (!cistrcmp(p, ".gif")) contentType = "image/gif";
  727.                     if (!cistrcmp(p, ".jpg")) contentType = "image/jpeg";
  728.                     if (!cistrcmp(p, ".jpeg")) contentType = "image/jpeg";
  729.                     if (!cistrcmp(p, ".mpg")) contentType = "video/mpeg";
  730.                     if (!cistrcmp(p, ".mpeg")) contentType = "video/mpeg";
  731.                 }
  732.  
  733.                 /* Create output file and start decoding */
  734.                 outfile = os_newtypedfile(fname, contentType, FILE_BINARY,
  735.                                           (params) 0);
  736.                 if (!outfile) {
  737.                     if (infile) fclose(infile);
  738.                     return 1;
  739.                 }
  740.                 state = st_decode;
  741.                 break;
  742.  
  743.             case st_inactive:   /* Looking for uuencoded data to resume */
  744.                 if (*buf != 'M' || strlen(buf) != line_length) {
  745.                     if (*buf == 'B' && !strncmp(buf, "BEGIN", 5)) {
  746.                         state = st_decode;
  747.                     }
  748.                     break;
  749.                 }
  750.                 state = st_decode;
  751.                 /* FALL THROUGH */
  752.             case st_decode:     /* Decoding data */
  753.                 if (line_length == 0) line_length = strlen(buf);
  754.                 if (*buf == 'M' && strlen(buf) == line_length) {
  755.                     uudecodeline(buf, outfile);
  756.                     break;
  757.                 }
  758.                 if (strlen(buf) > line_length) {
  759.                     state = st_inactive;
  760.                     break;
  761.                 }
  762.                 /*
  763.                  * May be on nearing end of file.
  764.                  * Save this line in case we are.
  765.                  */
  766.                 strcpy(lastline, buf);
  767.                 if (*buf == ' ' || *buf == '`') {
  768.                     state = st_last;
  769.                 }
  770.                 else {
  771.                     state = st_nextlast;
  772.                 }
  773.                 break;
  774.  
  775.             case st_nextlast:   /* May be nearing end of file */
  776.                 if (*buf == ' ' || *buf == '`') {
  777.                     state = st_last;
  778.                 }
  779.                 else {
  780.                     state = st_inactive;
  781.                 }
  782.                 break;
  783.  
  784.             case st_last:       /* Should be at end of file */
  785.                 if (!strncmp(buf, "end", 3) && isspace(buf[3])) {
  786.                     /* Handle that last line we saved */
  787.                     uudecodeline(lastline, outfile);
  788.                     if (infile) fclose(infile);
  789.                     os_closetypedfile(outfile);
  790.                     for (;part <= nparts; part++) {
  791.                         sprintf(buf, "%s%d", dir, part);
  792.                         remove(buf);
  793.                     }
  794.                     sprintf(buf, "%sCT", dir);
  795.                     remove(buf);
  796.                     os_donewithdir(dir);
  797.                     return 0;
  798.                 }
  799.                 state = st_inactive;
  800.                 break;
  801.  
  802.             case st_binhex:
  803.                 if (strncmp(buf, "---", 3)) break;
  804.                 inpart = part_init(infile);
  805.                 os_binhex(inpart, part, nparts);
  806.                 part_close(inpart);
  807.                 goto endbinhex;
  808.             }
  809.         }
  810.         if (state != st_binhex) state = st_inactive;
  811.         if (infile) fclose(infile);
  812.     endbinhex:
  813.         sprintf(buf, "%s%d", dir, part);
  814.         remove(buf);
  815.     }
  816.     if (outfile) os_closetypedfile(outfile);
  817.     if (state == st_binhex) os_binhex(0, 0, 0);
  818.     sprintf(buf, "%sCT", dir);
  819.     remove(buf);
  820.     os_donewithdir(dir);
  821.     return 0;
  822. }
  823.  
  824. #define DEC(c)  (((c) - ' ') & 077)
  825.  
  826. /*
  827.  * Decode a uuencoded line to 'outfile'
  828.  */
  829. uudecodeline(char *line, FILE *outfile)
  830. {
  831.     int c, len;
  832.  
  833.     len = DEC(*line++);
  834.     while (len) {
  835.         c = DEC(*line) << 2 | DEC(line[1]) >> 4;
  836.         putc(c, outfile);
  837.         if (--len) {
  838.             c = DEC(line[1]) << 4 | DEC(line[2]) >> 2;
  839.             putc(c, outfile);
  840.             if (--len) {
  841.                 c = DEC(line[2]) << 6 | DEC(line[3]);
  842.                 putc(c, outfile);
  843.                 len--;
  844.             }
  845.         }
  846.         line += 4;
  847.     }
  848.     return;
  849. }
  850.  
  851.     
  852.