]> diplodocus.org Git - nmh/commitdiff
Factored out base64 decoding code into decodeBase64().
authorDavid Levine <levinedl@acm.org>
Thu, 21 Jul 2016 01:25:01 +0000 (21:25 -0400)
committerDavid Levine <david.levine@gonift.com>
Thu, 21 Jul 2016 01:32:12 +0000 (21:32 -0400)
(cherry picked from commit 522c66eae1a144e0631dfaa61f1632e341ea7ce6)

h/prototypes.h
sbr/base64.c
test/mhstore/test-mhstore
uip/mhparse.c

index a5091591387c58501abc5bc07e3378c0d388328d..18ffb19d34ae5d2574377286463626ef3c828e9f 100644 (file)
@@ -473,6 +473,14 @@ int writeBase64aux(FILE *in, FILE *out, int crlf);
 int writeBase64 (unsigned char *, size_t, unsigned char *);
 int writeBase64raw (unsigned char *, size_t, unsigned char *);
 
+/*
+ * first argument:  the string to be decoded
+ * second argument:  the decoded bytes
+ * third argument:   non-zero for text content, and for which CR's should be skipped
+ * fourth argument:  for an MD5 digest, it can be null
+ */
+int decodeBase64 (const char *, const char **, size_t *, int, unsigned char *);
+
 /*
  * credentials management
  */
index 3a578c21bd2794faeb8bdd570859014788505a08..80ca86e08726bb4f8774e7929fb80524bf3ab73a 100644 (file)
@@ -8,6 +8,8 @@
 
 #include <h/mh.h>
 #include <h/mime.h>
+#include <h/md5.h>
+#include <inttypes.h>
 
 static char nib2b64[0x40+1] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -160,7 +162,7 @@ writeBase64 (unsigned char *in, size_t length, unsigned char *out)
     return OK;
 }
 
-/* 
+/*
  * Essentially a duplicate of writeBase64, but without line wrapping or
  * newline termination (note: string IS NUL terminated)
  */
@@ -205,3 +207,120 @@ writeBase64raw (unsigned char *in, size_t length, unsigned char *out)
 
     return OK;
 }
+
+
+static unsigned char b642nib[0x80] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+    0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+    0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+    0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+    0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+/*
+ * Decode a base64 string.  The result, decoded, must be freed by the caller.
+ * See description of arguments with declaration in h/prototypes.h.
+ */
+int
+decodeBase64 (const char *encoded, const char **decoded, size_t *len, int skip_crs,
+              unsigned char *digest) {
+    const char *cp = encoded;
+    int self_delimiting = 0;
+    int bitno, skip;
+    uint32_t bits;
+    /* Size the decoded string very conservatively. */
+    charstring_t decoded_c = charstring_create (strlen (encoded));
+    MD5_CTX mdContext;
+
+    if (digest) { MD5Init (&mdContext); }
+
+    bitno = 18;
+    bits = 0L;
+    skip = 0;
+
+    for (; *cp; ++cp) {
+        switch (*cp) {
+            unsigned char value;
+
+            default:
+                if (isspace ((unsigned char) *cp)) {
+                    break;
+                }
+                if (skip  ||  (((unsigned char) *cp) & 0x80)  ||
+                    (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) {
+                    advise (NULL, "invalid BASE64 encoding in %s", encoded);
+                    charstring_free (decoded_c);
+                    *decoded = NULL;
+
+                    return NOTOK;
+                }
+
+                bits |= value << bitno;
+test_end:
+                if ((bitno -= 6) < 0) {
+                    char b = (char) ((bits >> 16) & 0xff);
+
+                    if (! skip_crs  ||  b != '\r') {
+                        charstring_push_back (decoded_c, b);
+                    }
+                    if (digest) { MD5Update (&mdContext, (unsigned char *) &b, 1); }
+                    if (skip < 2) {
+                        b = (bits >> 8) & 0xff;
+                        if (! skip_crs  ||  b != '\r') {
+                            charstring_push_back (decoded_c, b);
+                        }
+                        if (digest) { MD5Update (&mdContext, (unsigned char *) &b, 1); }
+                        if (skip < 1) {
+                            b = bits & 0xff;
+                            if (! skip_crs  ||  b != '\r') {
+                                charstring_push_back (decoded_c, b);
+                            }
+                            if (digest) { MD5Update (&mdContext, (unsigned char *) &b, 1); }
+                        }
+                    }
+
+                    bitno = 18;
+                    bits = 0L;
+                    skip = 0;
+                }
+                break;
+
+            case '=':
+                if (++skip > 3) {
+                    self_delimiting = 1;
+                    break;
+                } else {
+                    goto test_end;
+                }
+        }
+    }
+
+    if (! self_delimiting  &&  bitno != 18) {
+        advise (NULL, "premature ending (bitno %d) in %s", bitno, encoded);
+        charstring_free (decoded_c);
+        *decoded = NULL;
+
+        return NOTOK;
+    }
+
+    *decoded = charstring_buffer_copy (decoded_c);
+    *len = charstring_bytes (decoded_c);
+    charstring_free (decoded_c);
+
+    if (digest) {
+        MD5Final (digest, &mdContext);
+    }
+
+    return OK;
+}
index fdf950ab072dbd71fd73e5fed2363f1bc618686c..6e8c52ae814956a53305732d08b5cf11a99ed4e7 100755 (executable)
@@ -17,6 +17,7 @@ fi
 setup_test
 
 expected="$MH_TEST_DIR/test-mhstore$$.expected"
+expected2="$MH_TEST_DIR/test-mhstore$$.expected2"
 expected_err="$MH_TEST_DIR/test-mhmail$$.expected_err"
 actual="$MH_TEST_DIR/test-mhstore$$.actual"
 actual_err="$MH_TEST_DIR/test-mhmail$$.actual_err"
@@ -117,6 +118,7 @@ This is the third text/plain part.
 ------- =_aaaaaaaaaa0
 Content-Type: text/plain; charset="iso-8859-1"; name="test4.txt"
 Content-Disposition: attachment; filename="test4.txt"
+Content-MD5: cMI1lB/LZ4jgVl3EbhdyWA==
 Content-Transfer-Encoding: base64
 
 VGhpcyBpcyB0aGUgZm91cnRoIHRleHQvcGxhaW4gcGFydC4NClRoaXMgdGVzdCBoYXMgbXVsdGlw
@@ -200,6 +202,15 @@ run_test 'mhstore last -part 2 -check' \
          'storing message 11 part 2 as file 11.2.txt'
 check "$expected" 11.2.txt 'keep first'
 
+# check with -check on base64 encoded part
+cat >"$expected2" <<'EOF'
+This is the fourth text/plain part.
+This test has multiple lines.
+EOF
+run_test 'mhstore last -part 4 -check' \
+         'storing message 11 part 4 as file 11.4.txt'
+check "$expected2" 11.4.txt
+
 # check -check with bad MD5 checksum
 sed 's/\(Content-MD5: \)kq+Hnc/\1BADBAD/' "$msgfile" >"$MH_TEST_DIR/$$.tmp"
 mv -f "$MH_TEST_DIR/$$.tmp" "$msgfile"
index ca5558ae486551dee77e7576d94bea77e41136be..984b1430aac6be2fc997f5dcb8525c7c87f76fdf 100644 (file)
@@ -648,7 +648,7 @@ get_ctinfo (char *cp, CT ct, int magic)
     *dp = c, cp = dp;
 
     if (!*ci->ci_type) {
-       advise (NULL, "invalid %s: field in message %s (empty type)", 
+       advise (NULL, "invalid %s: field in message %s (empty type)",
                TYPE_FIELD, ct->c_file);
        return NOTOK;
     }
@@ -1494,14 +1494,14 @@ invalid_param:
                        && p->c_ceopenfnx == openMail) {
                    int cc, size;
                    char *bp;
-                   
+
                    if ((size = ct->c_end - p->c_begin) <= 0) {
                        if (!e->eb_subject)
                            content_error (NULL, ct,
                                           "empty body for access-type=mail-server");
                        goto no_body;
                    }
-                   
+
                    e->eb_body = bp = mh_xmalloc ((unsigned) size);
                    fseek (p->c_fp, p->c_begin, SEEK_SET);
                    while (size > 0)
@@ -1735,26 +1735,6 @@ size_encoding (CT ct)
  * BASE64
  */
 
-static unsigned char b642nib[0x80] = {
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
-    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
-    0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
-    0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
-    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
-    0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
-    0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
-    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
-    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
-    0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
-};
-
-
 static int
 InitBase64 (CT ct)
 {
@@ -1765,15 +1745,15 @@ InitBase64 (CT ct)
 static int
 openBase64 (CT ct, char **file)
 {
-    int        bitno, cc, digested;
-    int fd, len, skip, own_ct_fp = 0, text = ct->c_type == CT_TEXT;
-    uint32_t bits;
-    unsigned char value, b;
-    char *cp, *ep, buffer[BUFSIZ];
+    ssize_t cc, len;
+    int fd, own_ct_fp = 0;
+    char *cp, *buffer = NULL;
     /* sbeck -- handle suffixes */
     CI ci;
     CE ce = &ct->c_cefile;
-    MD5_CTX mdContext;
+    const char *decoded;
+    size_t decoded_len;
+    unsigned char digest[16];
 
     if (ce->ce_fp) {
        fseek (ce->ce_fp, 0L, SEEK_SET);
@@ -1824,6 +1804,8 @@ openBase64 (CT ct, char **file)
     if ((len = ct->c_end - ct->c_begin) < 0)
        adios (NULL, "internal error(1)");
 
+    buffer = mh_xmalloc (len + 1);
+
     if (! ct->c_fp) {
        if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
            content_error (ct->c_file, ct, "unable to open for reading");
@@ -1831,17 +1813,11 @@ openBase64 (CT ct, char **file)
        }
        own_ct_fp = 1;
     }
-    
-    if ((digested = ct->c_digested))
-       MD5Init (&mdContext);
-
-    bitno = 18;
-    bits = 0L;
-    skip = 0;
 
     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
+    cp = buffer;
     while (len > 0) {
-       switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
+       switch (cc = read (fd, cp, len)) {
        case NOTOK:
            content_error (ct->c_file, ct, "error reading from");
            goto clean_up;
@@ -1854,75 +1830,40 @@ openBase64 (CT ct, char **file)
            if (cc > len)
                cc = len;
            len -= cc;
-
-           for (ep = (cp = buffer) + cc; cp < ep; cp++) {
-               switch (*cp) {
-               default:
-                   if (isspace ((unsigned char) *cp))
-                       break;
-                   if (skip || (((unsigned char) *cp) & 0x80)
-                       || (value = b642nib[((unsigned char) *cp) & 0x7f]) > 0x3f) {
-                       if (debugsw) {
-                           fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
-                               (unsigned char) *cp,
-                               (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
-                               skip);
-                       }
-                       content_error (NULL, ct,
-                                      "invalid BASE64 encoding -- continuing");
-                       continue;
-                   }
-
-                   bits |= value << bitno;
-test_end:
-                   if ((bitno -= 6) < 0) {
-                       b = (bits >> 16) & 0xff;
-                       if (!text || b != '\r')
-                           putc ((char) b, ce->ce_fp);
-                       if (digested)
-                           MD5Update (&mdContext, &b, 1);
-                       if (skip < 2) {
-                           b = (bits >> 8) & 0xff;
-                           if (! text || b != '\r')
-                               putc ((char) b, ce->ce_fp);
-                           if (digested)
-                               MD5Update (&mdContext, &b, 1);
-                           if (skip < 1) {
-                               b = bits & 0xff;
-                               if (! text || b != '\r')
-                                   putc ((char) b, ce->ce_fp);
-                               if (digested)
-                                   MD5Update (&mdContext, &b, 1);
-                           }
-                       }
-
-                       if (ferror (ce->ce_fp)) {
-                           content_error (ce->ce_file, ct,
-                                          "error writing to");
-                           goto clean_up;
-                       }
-                       bitno = 18, bits = 0L, skip = 0;
-                   }
-                   break;
-
-               case '=':
-                   if (++skip > 3)
-                       goto self_delimiting;
-                   goto test_end;
-               }
-           }
-       }
+            cp += cc;
+        }
     }
 
-    if (bitno != 18) {
-       if (debugsw)
-           fprintf (stderr, "premature ending (bitno %d)\n", bitno);
+    /* decodeBase64() requires null-terminated input. */
+    *cp = '\0';
 
-       content_error (NULL, ct, "invalid BASE64 encoding");
-       goto clean_up;
+    if (decodeBase64 (buffer, &decoded, &decoded_len, ct->c_type == CT_TEXT,
+                      ct->c_digested ? digest : NULL) == OK) {
+        size_t i;
+        const char *decoded_p = decoded;
+        for (i = 0; i < decoded_len; ++i) {
+            putc (*decoded_p++, ce->ce_fp);
+        }
+        if (ferror (ce->ce_fp)) {
+            content_error (ce->ce_file, ct, "error writing to");
+            goto clean_up;
+        }
+
+        if (ct->c_digested) {
+            if (memcmp(digest, ct->c_digest,
+                       sizeof(digest) / sizeof(digest[0]))) {
+                content_error (NULL, ct,
+                               "content integrity suspect (digest mismatch) -- continuing");
+            } else {
+                if (debugsw) {
+                    fprintf (stderr, "content integrity confirmed\n");
+                }
+            }
+        }
+    } else {
+        goto clean_up;
     }
 
-self_delimiting:
     fseek (ct->c_fp, 0L, SEEK_SET);
 
     if (fflush (ce->ce_fp)) {
@@ -1930,19 +1871,6 @@ self_delimiting:
        goto clean_up;
     }
 
-    if (digested) {
-       unsigned char digest[16];
-
-       MD5Final (digest, &mdContext);
-       if (memcmp((char *) digest, (char *) ct->c_digest,
-                  sizeof(digest) / sizeof(digest[0])))
-           content_error (NULL, ct,
-                          "content integrity suspect (digest mismatch) -- continuing");
-       else
-           if (debugsw)
-               fprintf (stderr, "content integrity confirmed\n");
-    }
-
     fseek (ce->ce_fp, 0L, SEEK_SET);
 
 ready_to_go:
@@ -1951,6 +1879,7 @@ ready_to_go:
       fclose (ct->c_fp);
       ct->c_fp = NULL;
     }
+    free (buffer);
     return fileno (ce->ce_fp);
 
 clean_up:
@@ -1959,6 +1888,7 @@ clean_up:
       ct->c_fp = NULL;
     }
     free_encoding (ct, 0);
+    free (buffer);
     return NOTOK;
 }
 
@@ -1976,18 +1906,18 @@ static char hex2nib[0x80] = {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
+    0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
+    0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 };
 
 
-static int 
+static int
 InitQuoted (CT ct)
 {
     return init_encoding (ct, openQuoted);
@@ -2968,79 +2898,45 @@ openURL (CT ct, char **file)
     return fd;
 }
 
+
+/*
+ * Stores MD5 digest (in cp, from Content-MD5 header) in ct->c_digest.  It
+ * has to be base64 decoded.
+ */
 static int
 readDigest (CT ct, char *cp)
 {
-    int        bitno, skip;
-    uint32_t bits;
-    char *bp = cp;
-    unsigned char *dp, value, *ep;
-
-    bitno = 18;
-    bits = 0L;
-    skip = 0;
-
-    for (ep = (dp = ct->c_digest)
-                + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
-       switch (*cp) {
-           default:
-               if (skip
-                       || (*cp & 0x80)
-                       || (value = b642nib[*cp & 0x7f]) > 0x3f) {
-                   if (debugsw)
-                       fprintf (stderr, "invalid BASE64 encoding\n");
-                   return NOTOK;
-               }
+    const char *digest;
 
-               bits |= value << bitno;
-test_end:
-               if ((bitno -= 6) < 0) {
-                   if (dp + (3 - skip) > ep)
-                       goto invalid_digest;
-                   *dp++ = (bits >> 16) & 0xff;
-                   if (skip < 2) {
-                       *dp++ = (bits >> 8) & 0xff;
-                       if (skip < 1)
-                           *dp++ = bits & 0xff;
-                   }
-                   bitno = 18;
-                   bits = 0L;
-                   skip = 0;
-               }
-               break;
+    size_t len;
+    if (decodeBase64 (cp, &digest, &len, 0, NULL) == OK) {
+        const size_t maxlen = sizeof ct->c_digest / sizeof ct->c_digest[0];
 
-           case '=':
-               if (++skip > 3)
-                   goto self_delimiting;
-               goto test_end;
-       }
-    if (bitno != 18) {
-       if (debugsw)
-           fprintf (stderr, "premature ending (bitno %d)\n", bitno);
+        if (strlen (digest) <= maxlen) {
+            memcpy (ct->c_digest, digest, maxlen);
 
-       return NOTOK;
-    }
-self_delimiting:
-    if (dp != ep) {
-invalid_digest:
-       if (debugsw) {
-           while (*cp)
-               cp++;
-           fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
-                    (int)(cp - bp));
-       }
+            if (debugsw) {
+                size_t i;
 
-       return NOTOK;
-    }
+                fprintf (stderr, "MD5 digest=");
+                for (i = 0; i < maxlen; ++i) {
+                    fprintf (stderr, "%02x", ct->c_digest[i] & 0xff);
+                }
+                fprintf (stderr, "\n");
+            }
 
-    if (debugsw) {
-       fprintf (stderr, "MD5 digest=");
-       for (dp = ct->c_digest; dp < ep; dp++)
-           fprintf (stderr, "%02x", *dp & 0xff);
-       fprintf (stderr, "\n");
-    }
+            return OK;
+        } else {
+            if (debugsw) {
+                fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
+                         (int) strlen (digest));
+            }
 
-    return OK;
+            return NOTOK;
+        }
+    } else {
+        return NOTOK;
+    }
 }