+static void decode_header_bq(char *it);
+typedef int (*header_fn_t)(char *);
+struct header_def {
+ const char *name;
+ header_fn_t func;
+ int namelen;
+};
+
+static void check_header(char *line, struct header_def *header)
+{
+ int i;
+
+ if (header[0].namelen <= 0) {
+ for (i = 0; header[i].name; i++)
+ header[i].namelen = strlen(header[i].name);
+ }
+ for (i = 0; header[i].name; i++) {
+ int len = header[i].namelen;
+ if (!strncasecmp(line, header[i].name, len) &&
+ line[len] == ':' && isspace(line[len + 1])) {
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ decode_header_bq(line + len + 2);
+ header[i].func(line + len + 2);
+ break;
+ }
+ }
+}
+
+static void check_subheader_line(char *line)
+{
+ static struct header_def header[] = {
+ { "Content-Type", handle_subcontent_type },
+ { "Content-Transfer-Encoding",
+ handle_content_transfer_encoding },
+ { NULL },
+ };
+ check_header(line, header);
+}
+static void check_header_line(char *line)
+{
+ static struct header_def header[] = {
+ { "From", handle_from },
+ { "Date", handle_date },
+ { "Subject", handle_subject },
+ { "Content-Type", handle_content_type },
+ { "Content-Transfer-Encoding",
+ handle_content_transfer_encoding },
+ { NULL },
+ };
+ check_header(line, header);
+}
+
+static int is_rfc2822_header(char *line)
+{
+ /*
+ * The section that defines the loosest possible
+ * field name is "3.6.8 Optional fields".
+ *
+ * optional-field = field-name ":" unstructured CRLF
+ * field-name = 1*ftext
+ * ftext = %d33-57 / %59-126
+ */
+ int ch;
+ char *cp = line;
+ while ((ch = *cp++)) {
+ if (ch == ':')
+ return cp != line;
+ if ((33 <= ch && ch <= 57) ||
+ (59 <= ch && ch <= 126))
+ continue;
+ break;
+ }
+ return 0;
+}
+
+static int read_one_header_line(char *line, int sz, FILE *in)
+{
+ int ofs = 0;
+ while (ofs < sz) {
+ int peek, len;
+ if (fgets(line + ofs, sz - ofs, in) == NULL)
+ break;
+ len = eatspace(line + ofs);
+ if (len == 0)
+ break;
+ if (!is_rfc2822_header(line)) {
+ /* Re-add the newline */
+ line[ofs + len] = '\n';
+ line[ofs + len + 1] = '\0';
+ break;
+ }
+ ofs += len;
+ /* Yuck, 2822 header "folding" */
+ peek = fgetc(in); ungetc(peek, in);
+ if (peek != ' ' && peek != '\t')
+ break;
+ }
+ /* Count mbox From headers as headers */
+ if (!ofs && !memcmp(line, "From ", 5))
+ ofs = 1;
+ return ofs;
+}
+
+static unsigned hexval(int c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return ~0;
+}
+
+static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047)
+{
+ int c;
+ while ((c = *in++) != 0 && (in <= ep)) {
+ if (c == '=') {
+ int d = *in++;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ *ot++ = ((hexval(d) << 4) | hexval(*in++));
+ continue;
+ }
+ if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+ c = 0x20;
+ *ot++ = c;
+ }
+ *ot = 0;
+ return 0;
+}
+
+static int decode_b_segment(char *in, char *ot, char *ep)
+{
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+
+ while ((c = *in++) != 0 && (in <= ep)) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else if (c == '=') {
+ /* padding is almost like (c == 0), except we do
+ * not output NUL resulting only from it;
+ * for now we just trust the data.
+ */
+ c = 0;
+ }
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ *ot++ = (acc | (c >> 4));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ *ot++ = (acc | (c >> 2));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ *ot++ = (acc | c);
+ acc = pos = 0;
+ break;
+ }
+ }
+ *ot = 0;
+ return 0;
+}
+
+static void convert_to_utf8(char *line, char *charset)