+ if (prefixlen)
+ strcat(q, "\n");
+
+ free(*value);
+
+ *value = output;
+
+ return 0;
+}
+
+/*
+ * Encode our specified header (or field) using base64.
+ *
+ * This is a little easier since every character gets encoded, we can
+ * calculate the line wrap up front.
+ */
+
+static int
+field_encode_base64(const char *name, char **value, const char *charset)
+{
+ int prefixlen = name ? strlen(name) + 2 : 0, charsetlen = strlen(charset);
+ int outlen = 0, numencode, curlen;
+ char *output = NULL, *p = *value, *q = NULL, *linestart;
+
+ /*
+ * Skip over any leading white space.
+ */
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ /*
+ * If we had a zero-length prefix, then just encode the whole field
+ * as-is, without line wrapping. Note that in addition to the encoding
+ *
+ * The added length we need is =? + charset + ?B? ... ?=
+ *
+ * That's 7 + strlen(charset) + 2 (for \n NUL).
+ */
+
+ while (prefixlen && ((base64len(strlen(p)) + 7 + charsetlen +
+ prefixlen) > ENCODELINELIMIT)) {
+
+ /*
+ * Our very first time, don't pad the line in the front
+ *
+ * Note ENCODELINELIMIT is + 2 because of \n \0
+ */
+
+
+ if (! output) {
+ outlen += ENCODELINELIMIT + 2;
+ output = q = mh_xmalloc(outlen);
+ linestart = q - prefixlen; /* Yes, this is intentional */
+ } else {
+ int curstart = linestart - output;
+ curlen = q - output;
+
+ outlen += ENCODELINELIMIT + 2;
+ output = mh_xrealloc(output, outlen);
+ q = output + curlen;
+ linestart = output + curstart;
+ }
+
+ /*
+ * We should have enough space now, so prepend the encoding markers
+ * and character set information. The leading space is intentional.
+ */
+
+ q += snprintf(q, outlen - (q - output), " =?%s?B?", charset);
+
+ /*
+ * Find out how much room we have left on the line and see how
+ * many characters we can stuff in. The start of our line
+ * is marked by "linestart", so use that to figure out how
+ * many characters are left out of ENCODELINELIMIT. Reserve
+ * 2 characters for the end markers and calculate how many
+ * characters we can fit into that space given the base64
+ * encoding expansion.
+ */
+
+ numencode = strbase64(ENCODELINELIMIT - (q - linestart) - 2);
+
+ if (numencode <= 0) {
+ advise(NULL, "Internal error: tried to encode %d characters "
+ "in base64", numencode);
+ return 1;
+ }
+
+ /*
+ * RFC 2047 prohibits spanning multibyte characters across tokens.
+ * Right now we only check for UTF-8.
+ *
+ * So note the key here ... we want to make sure the character BEYOND
+ * our last character is not a continuation byte. If it's the start
+ * of a new multibyte character or a single-byte character, that's ok.
+ */
+
+ if (strcasecmp(charset, "UTF-8") == 0) {
+ /*
+ * p points to the start of our current buffer, so p + numencode
+ * is one past the last character to encode
+ */
+
+ while (numencode > 0 && ((*(p + numencode) & 0xc0) == 0x80))
+ numencode--;
+
+ if (numencode == 0) {
+ advise(NULL, "Internal error: could not find start of "
+ "UTF-8 character when base64 encoding header");
+ return 1;
+ }
+ }
+
+ if (writeBase64raw((unsigned char *) p, numencode,
+ (unsigned char *) q) != OK) {
+ advise(NULL, "Internal error: base64 encoding of header failed");
+ return 1;
+ }
+
+ p += numencode;
+ q += base64len(numencode);
+
+ /*
+ * This will point us at the beginning of the new line (trust me).
+ */
+
+ linestart = q + 3;
+
+ /*
+ * What's going on here? Well, we know we're continuing to the next
+ * line, so we want to add continuation padding. We also add the
+ * trailing marker for the RFC 2047 token at this time as well.
+ * This uses a trick of snprintf(); we tell it to print a zero-length
+ * string, but pad it out to prefixlen - 1 characters; that ends
+ * up always printing out the requested number of spaces. We use
+ * prefixlen - 1 because we always add a space on the starting
+ * token marker; this makes things work out correctly for the first
+ * line, which should have a space between the ':' and the start
+ * of the token.
+ *
+ * It's okay if you don't follow all of that.
+ */
+
+ q += snprintf(q, outlen - (q - output), "?=\n%*s", prefixlen - 1, "");
+ }
+
+ /*
+ * We're here if there is either no prefix, or we can fit it in less
+ * than ENCODELINELIMIT characters. Encode the whole thing.
+ */
+
+ outlen += prefixlen + 9 + charsetlen + base64len(strlen(p));
+ curlen = q - output;
+
+ output = mh_xrealloc(output, outlen);
+ q = output + curlen;
+
+ q += snprintf(q, outlen - (q - output), "%s=?%s?B?",
+ prefixlen ? " " : "", charset);
+
+ if (writeBase64raw((unsigned char *) p, strlen(p),
+ (unsigned char *) q) != OK) {
+ advise(NULL, "Internal error: base64 encoding of header failed");
+ return 1;
+ }
+
+ strcat(q, "?=");
+
+ if (prefixlen)
+ strcat(q, "\n");
+