+ inform("unable to close temporary file %s, continuing...", tempfile);
+ }
+
+ return status;
+}
+
+
+/*
+ * If base64-encoded content has a text trailer, return the location, relative
+ * to c->c_begin, where the valid base64 ends. And return the trailer in the
+ * addresses pointed to by remainderp. The caller is responsible for
+ * deallocating that. If no text trailer, return ct->c_end - ct->c_begin and
+ * leave remainderp unchanged.
+ */
+static size_t
+get_valid_base64 (CT ct, char **remainderp) {
+ const size_t len = ct->c_end - ct->c_begin;
+ char *buf, format[16];
+ size_t pos;
+ int fd;
+
+ if (! ct->c_fp && ((ct->c_fp = fopen (ct->c_file, "r")) == NULL)) {
+ advise (ct->c_file, "unable to open for reading");
+ return NOTOK;
+ }
+ if ((fd = fileno (ct->c_fp)) == -1 ||
+ lseek (fd, ct->c_begin, SEEK_SET) == (off_t) -1) {
+ advise (ct->c_file, "unable to seek in");
+ return NOTOK;
+ }
+ buf = mh_xmalloc(len + 1);
+ snprintf(format, sizeof format, "%%%luc", (unsigned long) len);
+ if (fscanf(ct->c_fp, format, buf) == EOF) {
+ advise (ct->c_file, "unable to read");
+ return NOTOK;
+ }
+ buf[len] = '\0';
+
+ pos = find_invalid_base64_pos(buf);
+
+ if (ct->c_begin + pos < (size_t) ct->c_end) {
+ *remainderp = mh_xstrdup(&buf[pos]);
+ } else {
+ pos = ct->c_end - ct->c_begin;
+ }
+ free(buf);
+
+ return pos;
+}
+
+
+/*
+ * Find position in byte string of invalid base64 code. Skip individual
+ * invalid characters because RFC 2045 Sec 6.8 says they should be ignored.
+ * The motivating use case is a text footer that was mistakenly applied to
+ * base64 content. Therefore, if any of these is found, return the position
+ * of:
+ * 1. The byte (or end) after one or two consecutive pad ('=') bytes.
+ * 2. The first of a pair of invalid base64 bytes.
+ *
+ * If the base64 code is valid, return the position of the null terminator.
+ *
+ * encoded - the base64-encoded string
+ */
+static size_t
+find_invalid_base64_pos (const char *encoded) {
+ const char *cp;
+ size_t pos;
+ bool found_pad = false;
+ unsigned int found_invalid = 0;
+
+ for (cp = encoded, pos = 0;
+ *cp && ! found_pad && found_invalid < 2;
+ ++cp, ++pos) {
+ if (isspace ((unsigned char) *cp) ||
+ isalnum ((unsigned char) *cp) ||
+ *cp == '+' || *cp == '/' || *cp == '=') {
+ /* Valid base64 byte. */
+ if (*cp == '=') {
+ /* "evidence that the end of the data has been reached"
+ according to RFC 2045 */
+ found_pad = true;
+ }
+ /* Require consecutive invalid bytes. Let decodeBase64() handle
+ individual ones. */
+ found_invalid = 0;
+ } else {
+ ++found_invalid;
+ }
+ }
+
+ if (found_pad && *cp && *cp == '=') {
+ /* Skip over last in pair of ==. */
+ ++cp, ++pos;
+ } else if (found_invalid == 2) {
+ /* If a pair of consecutive invalid bytes, back up to first one. */
+ --cp, --pos;
+ --cp, --pos;
+ }
+
+ /* Skip over any trailing whitespace. */
+ while (*cp && isspace((unsigned char) *cp)) {
+ ++cp, ++pos;
+ }
+
+ return pos;
+}
+
+
+/*
+ * Check for valid base64 encoding, and "fix" if invalid.
+ */
+static int
+check_base64_encoding (CT *ctp)
+{
+ char *remainder = NULL;
+ int status = OK;
+
+ /* If there's a footer after base64 content, set c_end to before it, and
+ store the footer in remainder. */
+ (*ctp)->c_end = (*ctp)->c_begin + get_valid_base64(*ctp, &remainder);
+
+ if (remainder != NULL) {
+ /* Move ct to a subpart of a new multipart/related, and add the
+ remainder as a new text/plain subpart of it. */
+ int ignore_message_mods = 0;
+
+ status = insert_into_new_mp_mixed(ctp, remainder, &ignore_message_mods);
+ free(remainder);