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

  1. /*
  2.  * Decode MIME parts.
  3.  */
  4. /* (C) Copyright 1993,1994 by Carnegie Mellon University
  5.  * All Rights Reserved.
  6.  *
  7.  * Permission to use, copy, modify, distribute, and sell this software
  8.  * and its documentation for any purpose is hereby granted without
  9.  * fee, provided that the above copyright notice appear in all copies
  10.  * and that both that copyright notice and this permission notice
  11.  * appear in supporting documentation, and that the name of Carnegie
  12.  * Mellon University not be used in advertising or publicity
  13.  * pertaining to distribution of the software without specific,
  14.  * written prior permission.  Carnegie Mellon University makes no
  15.  * representations about the suitability of this software for any
  16.  * purpose.  It is provided "as is" without express or implied
  17.  * warranty.
  18.  *
  19.  * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
  20.  * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  21.  * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
  22.  * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  23.  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
  24.  * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
  25.  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  26.  * SOFTWARE.  */
  27.  
  28. #include <stdio.h>
  29. #include <string.h>
  30. #include <ctype.h>
  31. #include "xmalloc.h"
  32. #include "common.h"
  33. #include "part.h"
  34. #include "md5.h"
  35.  
  36. extern char *os_idtodir();
  37. extern FILE *os_newtypedfile();
  38. extern char *md5contextTo64();
  39.  
  40. /* The possible content transfer encodings */
  41. enum encoding { enc_none, enc_qp, enc_base64 };
  42.  
  43. char *ParseHeaders();
  44. enum encoding parseEncoding();
  45. params ParseContent();
  46. char *getParam();
  47. char *getDispositionFilename();
  48.  
  49. /*
  50.  * Read and handle an RFC 822 message from the body-part 'inpart'.
  51.  */
  52. handleMessage(struct part *inpart, char *defaultContentType, int inAppleDouble, int extractText)
  53. {
  54.     char *headers, *subject, *contentType, *contentDisposition, *contentMD5;
  55.     enum encoding contentEncoding;
  56.     params contentParams;
  57.  
  58.     /* Parse the headers, getting the ones we're interested in */
  59.  
  60.     headers = ParseHeaders(inpart, &subject, &contentType, &contentEncoding,
  61.                            &contentDisposition, &contentMD5);
  62.     if (!headers) return 1;
  63.  
  64.  
  65.     /* If no content type, or a non-MIME content type, use the default */
  66.     if (!contentType || !strchr(contentType, '/')) {
  67.         contentType = defaultContentType;
  68.     }
  69.     contentParams = ParseContent(&contentType);
  70.  
  71.  
  72.  
  73.     if (!cistrcmp(contentType, "message/rfc822")) {
  74.         if (contentEncoding != enc_none) {
  75.             warn("ignoring invalid content encoding on message/rfc822");
  76.         }
  77.  
  78.         /* Simple recursion */
  79.         return handleMessage(inpart, "text/plain", 0, extractText);
  80.     }
  81.     else if (!cistrcmp(contentType, "message/partial")) {
  82.         if (contentEncoding != enc_none) {
  83.             warn("ignoring invalid content encoding on message/partial");
  84.         }
  85.         return handlePartial(inpart, headers, contentParams, extractText);
  86.     }
  87.     else if (!cistrncmp(contentType, "message/", 8)) {
  88.         /* Probably message/external.  We don't care--toss it */
  89.         return ignoreMessage(inpart);
  90.     }
  91.     else if (!cistrncmp(contentType, "multipart/", 10)) {
  92.         if (contentEncoding != enc_none) {
  93.             warn("ignoring invalid content encoding on multipart");
  94.         }
  95.         return handleMultipart(inpart, contentType, contentParams,
  96.                                extractText);
  97.     }
  98.     else if (part_depth(inpart) == 0 &&
  99.              !cistrncmp(contentType, "text/", 5) &&
  100.              contentEncoding == enc_none &&
  101.              !getDispositionFilename(contentDisposition) &&
  102.              !getParam(contentParams, "name")) {
  103.         /* top-level text message, handle as possible uuencoded file */
  104.         return handleUuencode(inpart, subject, extractText);
  105.     }
  106.     else if (!extractText && !inAppleDouble &&
  107.              !cistrncmp(contentType, "text/", 5) &&
  108.              !getDispositionFilename(contentDisposition) &&
  109.              !getParam(contentParams, "name")) {
  110.         return handleText(inpart, contentEncoding);
  111.     }
  112.     else {
  113.         /* Some sort of attachment, extract it */
  114.         return saveToFile(inpart, inAppleDouble, contentType, contentParams,
  115.                           contentEncoding, contentDisposition, contentMD5);
  116.     }
  117. }
  118.  
  119. /*
  120.  * Skip whitespace and RFC-822 comments.
  121.  */
  122. SkipWhitespace(char **s)
  123. {
  124.     char *p = *s;
  125.     int commentlevel = 0;
  126.  
  127.     while (*p && (isspace(*p) || *p == '(')) {
  128.         if (*p == '\n') {
  129.             p++;
  130.             if (*p != ' ' && *p != '\t') {
  131.                 *s = 0;
  132.                 return;
  133.             }
  134.         }
  135.         else if (*p == '(') {
  136.             p++;
  137.             commentlevel++;
  138.             while (commentlevel) {
  139.                 switch (*p) {
  140.                 case '\n':
  141.                     p++;
  142.                     if (*p == ' ' || *p == '\t') break;
  143.                     /* FALL THROUGH */
  144.                 case '\0':
  145.                     *s = 0;
  146.                     return;
  147.                     
  148.                 case '\\':
  149.                     p++;
  150.                     break;
  151.  
  152.                 case '(':
  153.                     commentlevel++;
  154.                     break;
  155.  
  156.                 case ')':
  157.                     commentlevel--;
  158.                     break;
  159.                 }
  160.                 p++;
  161.             }
  162.         }
  163.         else p++;
  164.     }
  165.     if (*p == 0) {
  166.         *s = 0;
  167.     }
  168.     else {
  169.         *s = p;
  170.     }
  171. }
  172.  
  173. /*
  174.  * Read and parse the headers of an RFC 822 message, returning them in
  175.  * a pointer to a static buffer.  The headers are read from 'inpart'.
  176.  * A pointer to the value of any Subject:, Content-Type:,
  177.  * Content-Disposition:, or Content-MD5: header is stored in the space
  178.  * pointed to by 'subjectp', 'contentTypep', contentDispositionp, and
  179.  * contentMD5p, respectively.  The Content-Transfer-Encoding is stored
  180.  * in the enum pointed to by 'contentEncodingp'.
  181.  */
  182. #define HEADGROWSIZE 1000
  183. char *ParseHeaders(struct part *inpart, char **subjectp, char **contentTypep, enum encoding *contentEncodingp,
  184.                    char **contentDispositionp, char **contentMD5p)
  185. {
  186.     static int alloced = 0;
  187.     static char *headers;
  188.     int left, len, i;
  189.     char *next, *val;
  190.  
  191.     /* Read headers into buffer pointed to by "headers" */
  192.     if (!alloced) {
  193.         headers = xmalloc(alloced = HEADGROWSIZE);
  194.     }
  195.     next = headers;
  196.     *next++ = '\n';             /* Leading newline to make matching header names easier */
  197.     left = alloced - 2;         /* Allow room for terminating null */
  198.  
  199.     while (part_gets(next, left, inpart) && (*next != '\n' || next[-1] != '\n')) {
  200.         len = strlen(next);
  201.  
  202.         if (next[-1] == '\n') {
  203.             /* Check for valid header-ness of "next" */
  204.             for (i = 0; i < len; i++) {
  205.                 if (next[i] == ':' ||
  206.                     next[i] <= ' ' || next[i] >= '\177') break;
  207.             }
  208.             if (i == 0 || next[i] != ':') {
  209.                 /* Check for header continuation line */
  210.                 if (next == headers+1 || (next[0] != ' ' && next[0] != '\t')) {
  211.                     /*
  212.                      * Not a valid header, push back on input stream
  213.                      * and stop reading input.
  214.                      */
  215.                     part_ungets(next, inpart);
  216.                     break;
  217.                 }
  218.             }
  219.         }
  220.  
  221.         left -= len;
  222.         next += len;
  223.  
  224.         if (left < 100) {
  225.             len = next - headers;
  226.             alloced += HEADGROWSIZE;
  227.             left += HEADGROWSIZE;
  228.             headers = xrealloc(headers, alloced);
  229.             next = headers + len;
  230.         }
  231.     }
  232.  
  233.     *next = '\0';
  234.  
  235.     /* Look for the headers we find particularly interesting */
  236.     *subjectp = *contentTypep = *contentDispositionp = *contentMD5p = 0;
  237.     *contentEncodingp = enc_none;
  238.     for (next = headers; *next; next++) {
  239.         if (*next == '\n') {
  240.             switch(next[1]) {
  241.             case 's':
  242.             case 'S':
  243.                 if (!cistrncmp(next+2, "ubject:", 7)) {
  244.                     val = next+9;
  245.                     SkipWhitespace(&val);
  246.                     if (val) *subjectp = val;
  247.                 }
  248.                 break;
  249.  
  250.             case 'c':
  251.             case 'C':
  252.                 if (!cistrncmp(next+2, "ontent-type:", 12)) {
  253.                     val = next+14;
  254.                     SkipWhitespace(&val);
  255.                     if (val) *contentTypep = val;
  256.                 }
  257.                 else if (!cistrncmp(next+2, "ontent-transfer-encoding:", 25)) {
  258.                     *contentEncodingp = parseEncoding(next+27);
  259.                 }
  260.                 else if (!cistrncmp(next+2, "ontent-disposition:", 19)) {
  261.                     val = next+21;
  262.                     SkipWhitespace(&val);
  263.                     if (val) *contentDispositionp = val;
  264.                 }
  265.                 else if (!cistrncmp(next+2, "ontent-md5:", 11)) {
  266.                     val = next+13;
  267.                     SkipWhitespace(&val);
  268.                     if (val) *contentMD5p = val;
  269.                 }
  270.             }
  271.         }
  272.     }
  273.     return headers;
  274. }
  275.  
  276. /*
  277.  * Parse the Content-Transfer-Encoding: value pointed to by 's'.
  278.  * Returns the appropriate encoding enum.
  279.  */
  280. enum encoding parseEncoding(char *s)
  281. {
  282.     SkipWhitespace(&s);
  283.     if (s) {
  284.         switch (*s) {
  285.         case 'q':
  286.         case 'Q':
  287.             if (!cistrncmp(s+1, "uoted-printable", 15) &&
  288.                 (isspace(s[16]) || s[16] == '(')) {
  289.                 return enc_qp;
  290.             }
  291.             break;
  292.  
  293.         case '7':
  294.         case '8':
  295.             if (!cistrncmp(s+1, "bit", 3) &&
  296.                 (isspace(s[4]) || s[4] == '(')) {
  297.                 return enc_none;
  298.             }
  299.             break;
  300.  
  301.         case 'b':
  302.         case 'B':
  303.             if (!cistrncmp(s+1, "ase64", 5) &&
  304.                 (isspace(s[6]) || s[6] == '(')) {
  305.                 return enc_base64;
  306.             }
  307.             if (!cistrncmp(s+1, "inary", 5) &&
  308.                 (isspace(s[6]) || s[6] == '(')) {
  309.                 return enc_none;
  310.             }
  311.         }
  312.         warn("ignoring unknown content transfer encoding\n");   
  313.     }
  314.     return enc_none;
  315. }
  316.  
  317. /*
  318.  * Parse the value of a Content-Type: header.
  319.  * 'headerp' points to a pointer to the input string.
  320.  * The pointer pointed to by 'headerp' is changed to point to
  321.  * a static buffer containing the content type stripped of whitespace
  322.  * and parameters.  The parameters are converted to a type suitable for
  323.  * getParm() and returned.
  324.  */
  325. #define PARAMGROWSIZE 10
  326. params ParseContent(char **headerp)
  327. {
  328.     char *header;
  329.     static int palloced = 0;
  330.     static char **param;
  331.     static int calloced = 0;
  332.     static char *cbuf;
  333.     char *p;
  334.     int nparam;
  335.  
  336.     p = header = *headerp;
  337.  
  338.     /* Find end of header, including continuation lines */
  339.     do {
  340.         p = strchr(p+1, '\n');
  341.     } while (p && isspace(p[1]));
  342.     if (!p) {
  343.         p = header + strlen(header);
  344.     }
  345.  
  346.     /* If necessary, allocate/grow cbuf to hold header. */
  347.     if (p - header >= calloced) {
  348.         calloced = p - header + 1;
  349.         if (calloced < 200) calloced = 200;
  350.         cbuf = xrealloc(cbuf, calloced);
  351.     }
  352.  
  353.     /* Copy header to cbuf */
  354.     strncpy(cbuf, header, p - header);
  355.     cbuf[p - header] = 0;
  356.     header = *headerp = cbuf;
  357.     
  358.     nparam = 0;
  359.  
  360.     /* Strip whitespace from content type */
  361.     /* ParseHeaders() stripped leading whitespace */
  362.     p = header;
  363.     while (header && *header && *header != ';') {
  364.         while (*header && !isspace(*header) && *header != '(' &&
  365.                *header != ';') {
  366.             *p++ = *header++;
  367.         }
  368.         SkipWhitespace(&header);
  369.     }
  370.     if (!header || !*header) return 0;
  371.     header++;
  372.     *p = '\0';
  373.     
  374.     /* Parse the parameters */
  375.     while (*header) {
  376.         SkipWhitespace(&header);
  377.         if (!header) break;
  378.  
  379.         if (nparam+1 >= palloced) {
  380.             palloced += PARAMGROWSIZE;
  381.             param = (char **) xrealloc((char *)param, palloced * sizeof(char *));
  382.         }
  383.         param[nparam++] = header;
  384.  
  385.         /* Find any separating semicolon.  Pay attention to quoted-strings */
  386.         while (*header && *header != ';') {
  387.             if (*header == '\"') {
  388.                 ++header;
  389.                 while (*header && *header != '\"') {
  390.                     if (*header == '\\') {
  391.                         ++header;
  392.                         if (!*header) break;
  393.                     }
  394.                     ++header;
  395.                 }
  396.                 if (!*header) break;
  397.             }
  398.             else if (*header == '(') {
  399.                 /* Convert comments to spaces */
  400.                 p = header;
  401.                 SkipWhitespace(&p);
  402.                 if (!p) {
  403.                     break;
  404.                 }
  405.                 while (header < p) *header++ = ' ';
  406.                 header--;
  407.             }
  408.             header++;
  409.         }
  410.         if (*header) *header++ = '\0';
  411.     }
  412.     param[nparam] = 0;
  413.     return param;
  414. }
  415.  
  416. /*
  417.  * Get the value of the parameter named 'key' from the content-type
  418.  * parameters 'cParams'.  Returns a pointer to a static bufer which
  419.  * contains the value, or null if no such parameter was found.
  420.  */
  421. #define VALUEGROWSIZE 100
  422. char *getParam(params cParams, char *key)
  423. {
  424.     static char *value;
  425.     static int alloced = 0;
  426.     int left;
  427.     int keylen = strlen(key);
  428.     char *from, *to;
  429.  
  430.     if (!cParams) return 0;
  431.  
  432.     if (!alloced) {
  433.         value = xmalloc(alloced = VALUEGROWSIZE);
  434.     }
  435.  
  436.     /* Find the named parameter */
  437.     while (*cParams) {
  438.         if (!cistrncmp(key, *cParams, keylen) &&
  439.             ((*cParams)[keylen] == '=' || isspace((*cParams)[keylen]))) break;
  440.         cParams++;
  441.     }
  442.     if (!*cParams) return 0;
  443.  
  444.     /* Skip over the "=" and any surrounding whitespace */
  445.     from = *cParams + keylen;
  446.     while (*from && isspace(*from)) from++;
  447.     if (*from++ != '=') return 0;
  448.     while (*from && isspace(*from)) from++;
  449.     if (!*from) return 0;
  450.  
  451.     /* Copy value into buffer */
  452.     to = value;
  453.     left = alloced - 1;
  454.     if (*from == '\"') {
  455.         /* Quoted-string */
  456.         from++;
  457.         while (*from && *from != '\"') {
  458.             if (!--left) {
  459.                 alloced += VALUEGROWSIZE;
  460.                 value = xrealloc(value, alloced);
  461.                 to = value + alloced - left - 2;
  462.             }
  463.             if (*from == '\\') {
  464.                 from++;
  465.                 if (!*from) return 0;
  466.             }
  467.             *to++ = *from++;
  468.         }
  469.         if (!*from) return 0;
  470.     }
  471.     else {
  472.         /* Just a token */
  473.         while (*from && !isspace(*from)) {
  474.             if (!--left) {
  475.                 alloced += VALUEGROWSIZE;
  476.                 value = xrealloc(value, alloced);
  477.                 to = value + alloced - left - 2;
  478.             }
  479.             *to++ = *from++;
  480.         }
  481.     }
  482.     *to = '\0';
  483.     return value;
  484. }
  485.  
  486. /*
  487.  * Get the value of the "filename" parameter in a Content-Disposition:
  488.  * header.  Returns a pointer to a static buffer containing the value, or
  489.  * a null pointer if there was no such parameter.
  490.  */
  491. char *
  492. getDispositionFilename(char *disposition)
  493. {
  494.     static char *value;
  495.     static int alloced = 0;
  496.     int left;
  497.     char *to;
  498.  
  499.     if (!disposition) return 0;
  500.  
  501.     /* Skip until we find ";" "filename" "=" tokens. */
  502.     for (;;) {
  503.         /* Skip until we find ";" */
  504.         while (*disposition != ';') {
  505.             if (!*disposition) return 0;
  506.             else if (*disposition == '\"') {
  507.                 ++disposition;
  508.                 while (*disposition && *disposition != '\"') {
  509.                     if (*disposition == '\\') {
  510.                         ++disposition;
  511.                         if (!*disposition) return 0;
  512.                     }
  513.                     ++disposition;
  514.                 }
  515.                 if (!*disposition) return 0;
  516.             }
  517.             else if (*disposition == '(') {
  518.                 SkipWhitespace(&disposition);
  519.                 if (!disposition) return 0;
  520.                 disposition--;
  521.             }
  522.             disposition++;
  523.         }
  524.  
  525.         /* Skip over ";" and trailing whitespace */
  526.         disposition++;
  527.         SkipWhitespace(&disposition);
  528.         if (!disposition) return 0;
  529.  
  530.         /*
  531.          * If we're not looking at a "filename" token, go back
  532.          * and look for another ";".  Otherwise skip it and
  533.          * trailing whitespace.
  534.          */
  535.         if (cistrncmp(disposition, "filename", 8) != 0) continue;
  536.         disposition += 8;
  537.         if (!isspace(*disposition) && *disposition != '=' &&
  538.             *disposition != '(') {
  539.             continue;
  540.         }
  541.         SkipWhitespace(&disposition);
  542.         if (!disposition) return 0;
  543.  
  544.         /* If we're looking at a ";", we found what we're looking for */
  545.         if (*disposition++ == '=') break;
  546.     }
  547.  
  548.     SkipWhitespace(&disposition);
  549.     if (!disposition) return 0;
  550.       
  551.     if (!alloced) {
  552.         value = xmalloc(alloced = VALUEGROWSIZE);
  553.     }
  554.  
  555.     /* Copy value into buffer */
  556.     to = value;
  557.     left = alloced - 1;
  558.     if (*disposition == '\"') {
  559.         /* Quoted-string */
  560.         disposition++;
  561.         while (*disposition && *disposition != '\"') {
  562.             if (!--left) {
  563.                 alloced += VALUEGROWSIZE;
  564.                 value = xrealloc(value, alloced);
  565.                 to = value + alloced - left - 2;
  566.             }
  567.             if (*disposition == '\\') {
  568.                 disposition++;
  569.                 if (!*disposition) return 0;
  570.             }
  571.             *to++ = *disposition++;
  572.         }
  573.         if (!*disposition) return 0;
  574.     }
  575.     else {
  576.         /* Just a token */
  577.         while (*disposition && !isspace(*disposition) &&
  578.                *disposition != '(') {
  579.             if (!--left) {
  580.                 alloced += VALUEGROWSIZE;
  581.                 value = xrealloc(value, alloced);
  582.                 to = value + alloced - left - 2;
  583.             }
  584.             *to++ = *disposition++;
  585.         }
  586.     }
  587.     *to = '\0';
  588.     return value;
  589. }    
  590.  
  591. /*
  592.  * Read and handle a message/partial object from the file 'inpart'.
  593.  */
  594. handlePartial(struct part *inpart, char *headers, params contentParams, int extractText)
  595. {
  596.     char *id, *dir, *p;
  597.     int thispart;
  598.     int nparts = 0;
  599.     char buf[1024];
  600.     FILE *partfile, *outfile;
  601.     struct part *outpart;
  602.     int i, docopy;
  603.  
  604.     id = getParam(contentParams, "id");
  605.     if (!id) {
  606.         warn("partial message has no id parameter");
  607.         goto ignore;
  608.     }
  609.  
  610.     /* Get directory to store the parts being reassembled */
  611.     dir = os_idtodir(id);
  612.     if (!dir) goto ignore;
  613.  
  614.     p = getParam(contentParams, "number");
  615.     if (!p) {
  616.         warn("partial message doesn't have number parameter");
  617.         goto ignore;
  618.     }
  619.     thispart = atoi(p);
  620.  
  621.     if (p = getParam(contentParams, "total")) {
  622.         nparts = atoi(p);
  623.         if (nparts <= 0) {
  624.             warn("partial message has invalid number of parts");
  625.             goto ignore;
  626.         }
  627.         /* Store number of parts in reassembly directory */
  628.         sprintf(buf, "%sCT", dir);
  629.         partfile = fopen(buf, "w");
  630.         if (!partfile) {
  631.             os_perror(buf);
  632.             goto ignore;
  633.         }
  634.         fprintf(partfile, "%d\n", nparts);
  635.         if (partfile) fclose(partfile);
  636.     }
  637.     else {
  638.         /* Try to retrieve number of parts from reassembly directory */
  639.         sprintf(buf, "%sCT", dir);
  640.         if (partfile = fopen(buf, "r")) {
  641.             if (fgets(buf, sizeof(buf), partfile)) {
  642.                 nparts = atoi(buf);
  643.                 if (nparts < 0) nparts = 0;
  644.             }
  645.             if (partfile) fclose(partfile);
  646.         }
  647.     }
  648.  
  649.     /* Sanity check */
  650.     if (thispart <= 0 || (nparts && thispart > nparts)) {
  651.         warn("partial message has invalid number");
  652.         goto ignore;
  653.     }
  654.  
  655.     sprintf(buf, "Saving part %d ", thispart);
  656.     if (nparts) sprintf(buf+strlen(buf), "of %d ", nparts);
  657.     strcat(buf, getParam(contentParams, "id"));
  658.     chat(buf);
  659.  
  660.     /* Create file to store this part */
  661.     sprintf(buf, "%s%d", dir, thispart);
  662.     partfile = fopen(buf, "w");
  663.     if (!partfile) {
  664.         os_perror(buf);
  665.         goto ignore;
  666.     }
  667.  
  668.     /* Do special-case header handling for first part */
  669.     if (thispart == 1) {
  670.         int skippedfirstbyte = 0;
  671.  
  672.         while (*headers) {
  673.             if (*headers == '\n' &&
  674.                 (!cistrncmp(headers, "\ncontent-", 9) ||
  675.                  !cistrncmp(headers, "\nmessage-id:", 12))) {
  676.                 /* Special case, skip header */
  677.                 headers++;
  678.                 while (*headers && (*headers != '\n' || isspace(headers[1]))) {
  679.                     headers++;
  680.                 }
  681.             }
  682.             else {
  683.                 /* First byte of headers is extra newline, don't write it to file */
  684.                 if (skippedfirstbyte++) putc(*headers, partfile);
  685.                 headers++;
  686.             }
  687.         }
  688.         docopy = 0;
  689.         /* Handle headers in the multipart/partial body */
  690.         while (part_gets(buf, sizeof(buf), inpart)) {
  691.             if (*buf == '\n') {
  692.                 putc('\n', partfile);
  693.                 break;
  694.             }
  695.             if (!cistrncmp(buf, "content-", 8) || !cistrncmp(buf, "message-id:", 11)) {
  696.                 docopy = 1;
  697.             }
  698.             else if (!isspace(*buf)) {
  699.                 docopy = 0;
  700.             }
  701.  
  702.             if (docopy) fputs(buf, partfile);
  703.             while(buf[strlen(buf)-1] != '\n' && part_gets(buf, sizeof(buf), inpart)) {
  704.                 if (docopy) fputs(buf, partfile);
  705.             }
  706.         }
  707.     }
  708.  
  709.     /* Copy the contents to the file */
  710.     while (part_gets(buf, sizeof(buf), inpart)) {
  711.         fputs(buf, partfile);
  712.     }
  713.     if (partfile) fclose(partfile);
  714.  
  715.     /* Check to see if we have all parts.  Start from the highest numbers
  716.      * as we are more likely not to have them.
  717.      */
  718.     for (i = nparts; i; i--) {
  719.         sprintf(buf, "%s%d", dir, i);
  720.         partfile = fopen(buf, "r");
  721.         if (partfile) {
  722.             fclose(partfile);
  723.         }
  724.         else {
  725.             break;
  726.         }
  727.     }
  728.  
  729.     if (i || !nparts) {
  730.         /* We don't have all the parts yet */
  731.         return 0;
  732.     }
  733.  
  734.     /* We have everything, concatenate all the parts into a single file */
  735.     sprintf(buf, "%sFULL", dir);
  736.     outfile = fopen(buf, "w");
  737.     if (!outfile) {
  738.         os_perror(buf);
  739.         return 1;
  740.     }
  741.     for (i=1; i<=nparts; i++) {
  742.         sprintf(buf, "%s%d", dir, i);
  743.         partfile = fopen(buf, "r");
  744.         if (!partfile) {
  745.             os_perror(buf);
  746.             return 1;
  747.         }
  748.         while (fgets(buf, sizeof(buf), partfile)) {
  749.             fputs(buf, outfile);
  750.         }
  751.         if (partfile) fclose(partfile);
  752.  
  753.         /* Done with message part file, delete it */
  754.         sprintf(buf, "%s%d", dir, i);
  755.         remove(buf);
  756.     }
  757.  
  758.     /* Open the concatenated file for reading and handle it */
  759.     if (outfile) fclose(outfile);
  760.     sprintf(buf, "%sFULL", dir);
  761.     outfile = fopen(buf, "r");
  762.     if (!outfile) {
  763.         os_perror(buf);
  764.         return 1;
  765.     }
  766.     outpart = part_init(outfile);
  767.     handleMessage(outpart, "text/plain", 0, extractText);
  768.     part_close(outpart);
  769.  
  770.     /* Clean up the rest of the reassembly directory */
  771.     sprintf(buf, "%sFULL", dir);
  772.     remove(buf);
  773.     sprintf(buf, "%sCT", dir);
  774.     remove(buf);
  775.     os_donewithdir(dir);
  776.  
  777.     return 0;
  778.  
  779.  ignore:
  780.     ignoreMessage(inpart);
  781.     return 1;
  782. }
  783.  
  784. /*
  785.  * Skip over a message object from the file 'inpart'.
  786.  */
  787. ignoreMessage(struct part *inpart)
  788. {
  789.     while (part_getc(inpart) != EOF);
  790.     return 0;
  791. }
  792.  
  793. /*
  794.  * Read and handle a multipart object from 'inpart'.
  795.  */
  796. handleMultipart(struct part *inpart, char *contentType, params contentParams, int extractText)
  797. {
  798.     char *id;
  799.     char *defaultContentType = "text/plain";
  800.     int isAppleDouble = 0;
  801.  
  802.     /* Components of multipart/digest have a different default content-type */
  803.     if (!cistrcmp(contentType, "multipart/digest")) {
  804.         defaultContentType = "message/rfc822";
  805.     }
  806.     if (!cistrcmp(contentType, "multipart/appledouble")) {
  807.         isAppleDouble++;
  808.     }
  809.  
  810.     if (!(id = getParam(contentParams, "boundary"))) {
  811.         warn("multipart message has no boundary parameter");
  812.         id="";
  813.     }
  814.  
  815.     /* Add the new boundary id */
  816.     part_addboundary(inpart, id);
  817.  
  818. #ifdef __riscos
  819.     /*
  820.      * "Marcel" encodes RISCOS directory structure in the multipart
  821.      * structure.  That is the Wrong Way to do it, but we hold our
  822.      * nose and pass the information to the OS layer.
  823.      */
  824.     os_boundaryhookopen(part_depth(inpart));
  825. #endif
  826.  
  827.     /*
  828.      * Skip over preamble.
  829.      * HACK: The initial boundary doesn't have to start with a newline,
  830.      * so we deal with this by stuffing an initial newline into the input
  831.      * stream
  832.      */
  833.     part_ungetc('\n', inpart);
  834.     ignoreMessage(inpart);
  835.  
  836.     /* Handle the component messages */
  837.     while (!part_readboundary(inpart)) {
  838.         handleMessage(inpart, defaultContentType, isAppleDouble, extractText);
  839.     }
  840.  
  841. #ifdef __riscos
  842.     os_boundaryhookclose(part_depth(inpart));
  843. #endif
  844.  
  845.     /* Skip over postamble */
  846.     ignoreMessage(inpart);
  847.  
  848.     /* Remove any lingering unused description file */
  849.     (void) remove(TEMPFILENAME);
  850.  
  851.     return 0;
  852. }
  853.  
  854. /*
  855.  * Handle a text message object from 'inpart' by saving it to
  856.  * the temporary description file.
  857.  */
  858. int handleText(struct part *inpart, enum encoding contentEncoding)
  859. {
  860.     FILE *descfile;
  861.  
  862.     descfile = fopen(TEMPFILENAME, "w");
  863.     if (!descfile) {
  864.         os_perror(TEMPFILENAME);
  865.         ignoreMessage(inpart);
  866.         return 1;
  867.     }
  868.  
  869.     /* Write the file, handling the appropriate encoding */
  870.     switch (contentEncoding) {
  871.     case enc_none:
  872.         fromnone(inpart, descfile, (char **)0);
  873.         break;
  874.  
  875.     case enc_qp:
  876.         fromqp(inpart, descfile, (char **)0);
  877.         break;
  878.  
  879.     case enc_base64:
  880.         from64(inpart, descfile, (char **)0, 1);
  881.         break;
  882.     }
  883.  
  884.     if (descfile) fclose(descfile);
  885.     return 0;
  886. }
  887.  
  888. /*
  889.  * Read a message object from 'inpart' and save it to a file.
  890.  */
  891. saveToFile(struct part *inpart, int inAppleDouble, char *contentType, params contentParams,
  892.            enum encoding contentEncoding, char *contentDisposition, char *contentMD5)
  893. {
  894.     FILE *outfile = 0;
  895.     int flags = 0;
  896.     int suppressCR = 0;
  897.     char *outputmd5;
  898.     char *fname;
  899.  
  900.  
  901.     if (!cistrncmp(contentType, "text/", 5)) {
  902.         suppressCR = 1;
  903.     }
  904.     else if (contentEncoding == enc_base64) {
  905.         /*
  906.          * HEURISTIC: It is not in general possible to determine whether
  907.          * any non-text content type is line-oriented.  We guess
  908.          * the "binary" status of a part from the composer's choice
  909.          * of content transfer encoding.
  910.          *
  911.          * If the content transfer encoding is "binary" and the input is
  912.          * not line-oriented, we're screwed anyway--the input file has
  913.          * been opened in text mode.  So the "binary output file" heuristic
  914.          * is not applied in this case.
  915.          */
  916.         flags |= FILE_BINARY;
  917.     }
  918.  
  919.  
  920.     if (inAppleDouble) flags |= FILE_INAPPLEDOUBLE;
  921.     
  922.     /* Find an appropriate filename and create the output file */
  923.     fname = getDispositionFilename(contentDisposition);
  924.     if (!fname) fname = getParam(contentParams, "name");
  925.     if (fname) fname = strsave(fname);
  926.     outfile = os_newtypedfile(fname, contentType, flags, contentParams);
  927.     if (fname) free(fname);
  928.     if (!outfile) {
  929.         ignoreMessage(inpart);
  930.         return 1;
  931.     }
  932.  
  933.     /* Write the file, handling the appropriate encoding */
  934.     switch (contentEncoding) {
  935.     case enc_none:
  936.         fromnone(inpart, outfile, &outputmd5);
  937.         break;
  938.  
  939.     case enc_qp:
  940.         fromqp(inpart, outfile, &outputmd5);
  941.         break;
  942.  
  943.     case enc_base64:
  944.         from64(inpart, outfile, &outputmd5, suppressCR);
  945.         break;
  946.     }
  947.     rewind(outfile);
  948.  
  949.     /* Check the MD5 digest if it was supplied */
  950.     if (contentMD5) {
  951.         if (strncmp(outputmd5, contentMD5, strlen(outputmd5)) != 0) {
  952.             os_warnMD5mismatch();
  953.         }
  954.     }
  955.     free(outputmd5);
  956.  
  957.     os_closetypedfile(outfile);
  958.     return 0;
  959. }
  960.  
  961. #define XX 127
  962. /*
  963.  * Table for decoding hexadecimal in quoted-printable
  964.  */
  965. static char index_hex[256] = {
  966.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  967.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  968.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  969.      0, 1, 2, 3,  4, 5, 6, 7,  8, 9,XX,XX, XX,XX,XX,XX,
  970.     XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  971.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  972.     XX,10,11,12, 13,14,15,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  973.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  974.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  975.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  976.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  977.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  978.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  979.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  980.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  981.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  982. };
  983. #define HEXCHAR(c)  (index_hex[(unsigned char)(c)])
  984.  
  985. /*
  986.  * Table for decoding base64
  987.  */
  988. static char index_64[256] = {
  989.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  990.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  991.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63,
  992.     52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX,
  993.     XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
  994.     15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX,
  995.     XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
  996.     41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,
  997.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  998.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  999.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  1000.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  1001.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  1002.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  1003.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  1004.     XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
  1005. };
  1006. #define CHAR64(c)  (index_64[(unsigned char)(c)])
  1007.  
  1008. from64(struct part *inpart, FILE *outfile, char **digestp, int suppressCR)
  1009. {
  1010.     int c1, c2, c3, c4;
  1011.     int DataDone = 0;
  1012.     char buf[3];
  1013.     MD5_CTX context;
  1014.  
  1015.     if (digestp) MD5Init(&context);
  1016.     while ((c1 = part_getc(inpart)) != EOF) {
  1017.         if (c1 != '=' && CHAR64(c1) == XX) {
  1018.             continue;
  1019.         }
  1020.         if (DataDone) continue;
  1021.         do {
  1022.             c2 = part_getc(inpart);
  1023.         } while (c2 != EOF && c2 != '=' && CHAR64(c2) == XX);
  1024.         do {
  1025.             c3 = part_getc(inpart);
  1026.         } while (c3 != EOF && c3 != '=' && CHAR64(c3) == XX);
  1027.         do {
  1028.             c4 = part_getc(inpart);
  1029.         } while (c4 != EOF && c4 != '=' && CHAR64(c4) == XX);
  1030.         if (c2 == EOF || c3 == EOF || c4 == EOF) {
  1031.             warn("Premature EOF");
  1032.             break;
  1033.         }
  1034.         if (c1 == '=' || c2 == '=') {
  1035.             DataDone=1;
  1036.             continue;
  1037.         }
  1038.         c1 = CHAR64(c1);
  1039.         c2 = CHAR64(c2);
  1040.         buf[0] = ((c1<<2) | ((c2&0x30)>>4));
  1041.         if (!suppressCR || buf[0] != '\r') putc(buf[0], outfile);
  1042.         if (c3 == '=') {
  1043.             if (digestp) MD5Update(&context, buf, 1);
  1044.             DataDone = 1;
  1045.         } else {
  1046.             c3 = CHAR64(c3);
  1047.             buf[1] = (((c2&0x0F) << 4) | ((c3&0x3C) >> 2));
  1048.             if (!suppressCR || buf[1] != '\r') putc(buf[1], outfile);
  1049.             if (c4 == '=') {
  1050.                 if (digestp) MD5Update(&context, buf, 2);
  1051.                 DataDone = 1;
  1052.             } else {
  1053.                 c4 = CHAR64(c4);
  1054.                 buf[2] = (((c3&0x03) << 6) | c4);
  1055.                 if (!suppressCR || buf[2] != '\r') putc(buf[2], outfile);
  1056.                 if (digestp) MD5Update(&context, buf, 3);               
  1057.             }
  1058.         }
  1059.     }
  1060.     if (digestp) *digestp = md5contextTo64(&context);
  1061. }
  1062.  
  1063. fromqp(struct part *inpart, FILE *outfile, char **digestp)
  1064. {
  1065.     int c1, c2;
  1066.     MD5_CTX context;
  1067.     char c;
  1068.  
  1069.     if (digestp) MD5Init(&context);
  1070.  
  1071.     while ((c1 = part_getc(inpart)) != EOF) {
  1072.         if (c1 == '=') {
  1073.             c1 = part_getc(inpart);
  1074.             if (c1 != '\n') {
  1075.                 c1 = HEXCHAR(c1);
  1076.                 c2 = part_getc(inpart);
  1077.                 c2 = HEXCHAR(c2);
  1078.                 c = c1<<4 | c2;
  1079.                 if (c != '\r') putc(c, outfile);
  1080.                 if (digestp) MD5Update(&context, &c, 1);
  1081.             }
  1082.         } else {
  1083.             putc(c1, outfile);
  1084.             if (c1 == '\n') {
  1085.                 if (digestp) MD5Update(&context, "\r", 1);
  1086.             }
  1087.             c = c1;
  1088.             if (digestp) MD5Update(&context, &c, 1);
  1089.         }
  1090.     }
  1091.     if (digestp) *digestp=md5contextTo64(&context);
  1092. }
  1093.  
  1094. fromnone(struct part *inpart, FILE *outfile, char **digestp)
  1095. {
  1096.     int c;
  1097.     char ch;
  1098.     MD5_CTX context;
  1099.  
  1100.     if (digestp) MD5Init(&context);
  1101.  
  1102.     while ((c = part_getc(inpart)) != EOF) {
  1103.         putc(c, outfile);
  1104.         if (c == '\n') {
  1105.             if (digestp) MD5Update(&context, "\r", 1);
  1106.         }
  1107.         ch = c;
  1108.         if (digestp) MD5Update(&context, &ch, 1);
  1109.     }
  1110.     if (digestp) *digestp=md5contextTo64(&context);
  1111. }
  1112.  
  1113.