"Fossies" - the Fresh Open Source Software Archive

Member "mpack-1.6/decode.c" (21 Jul 2003, 29676 Bytes) of package /linux/misc/old/mpack-1.6.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "decode.c" see the Fossies "Dox" file reference documentation.

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