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