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