builtin-mailinfo.con commit Merge branch 'ph/checkout' (99ea66e)
   1/*
   2 * Another stupid program, this one parsing the headers of an
   3 * email to figure out authorship and subject
   4 */
   5#include "cache.h"
   6#include "builtin.h"
   7#include "utf8.h"
   8#include "strbuf.h"
   9
  10static FILE *cmitmsg, *patchfile, *fin, *fout;
  11
  12static int keep_subject;
  13static const char *metainfo_charset;
  14static struct strbuf line = STRBUF_INIT;
  15static struct strbuf name = STRBUF_INIT;
  16static struct strbuf email = STRBUF_INIT;
  17
  18static enum  {
  19        TE_DONTCARE, TE_QP, TE_BASE64,
  20} transfer_encoding;
  21static enum  {
  22        TYPE_TEXT, TYPE_OTHER,
  23} message_type;
  24
  25static struct strbuf charset = STRBUF_INIT;
  26static int patch_lines;
  27static struct strbuf **p_hdr_data, **s_hdr_data;
  28
  29#define MAX_HDR_PARSED 10
  30#define MAX_BOUNDARIES 5
  31
  32static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
  33{
  34        struct strbuf *src = name;
  35        if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
  36                strchr(name->buf, '<') || strchr(name->buf, '>'))
  37                src = email;
  38        else if (name == out)
  39                return;
  40        strbuf_reset(out);
  41        strbuf_addbuf(out, src);
  42}
  43
  44static void parse_bogus_from(const struct strbuf *line)
  45{
  46        /* John Doe <johndoe> */
  47
  48        char *bra, *ket;
  49        /* This is fallback, so do not bother if we already have an
  50         * e-mail address.
  51         */
  52        if (email.len)
  53                return;
  54
  55        bra = strchr(line->buf, '<');
  56        if (!bra)
  57                return;
  58        ket = strchr(bra, '>');
  59        if (!ket)
  60                return;
  61
  62        strbuf_reset(&email);
  63        strbuf_add(&email, bra + 1, ket - bra - 1);
  64
  65        strbuf_reset(&name);
  66        strbuf_add(&name, line->buf, bra - line->buf);
  67        strbuf_trim(&name);
  68        get_sane_name(&name, &name, &email);
  69}
  70
  71static void handle_from(const struct strbuf *from)
  72{
  73        char *at;
  74        size_t el;
  75        struct strbuf f;
  76
  77        strbuf_init(&f, from->len);
  78        strbuf_addbuf(&f, from);
  79
  80        at = strchr(f.buf, '@');
  81        if (!at) {
  82                parse_bogus_from(from);
  83                return;
  84        }
  85
  86        /*
  87         * If we already have one email, don't take any confusing lines
  88         */
  89        if (email.len && strchr(at + 1, '@')) {
  90                strbuf_release(&f);
  91                return;
  92        }
  93
  94        /* Pick up the string around '@', possibly delimited with <>
  95         * pair; that is the email part.
  96         */
  97        while (at > f.buf) {
  98                char c = at[-1];
  99                if (isspace(c))
 100                        break;
 101                if (c == '<') {
 102                        at[-1] = ' ';
 103                        break;
 104                }
 105                at--;
 106        }
 107        el = strcspn(at, " \n\t\r\v\f>");
 108        strbuf_reset(&email);
 109        strbuf_add(&email, at, el);
 110        strbuf_remove(&f, at - f.buf, el + 1);
 111
 112        /* The remainder is name.  It could be "John Doe <john.doe@xz>"
 113         * or "john.doe@xz (John Doe)", but we have removed the
 114         * email part, so trim from both ends, possibly removing
 115         * the () pair at the end.
 116         */
 117        strbuf_trim(&f);
 118        if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
 119                strbuf_remove(&f, 0, 1);
 120                strbuf_setlen(&f, f.len - 1);
 121        }
 122
 123        get_sane_name(&name, &f, &email);
 124        strbuf_release(&f);
 125}
 126
 127static void handle_header(struct strbuf **out, const struct strbuf *line)
 128{
 129        if (!*out) {
 130                *out = xmalloc(sizeof(struct strbuf));
 131                strbuf_init(*out, line->len);
 132        } else
 133                strbuf_reset(*out);
 134
 135        strbuf_addbuf(*out, line);
 136}
 137
 138/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt
 139 * to have enough heuristics to grok MIME encoded patches often found
 140 * on our mailing lists.  For example, we do not even treat header lines
 141 * case insensitively.
 142 */
 143
 144static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
 145{
 146        const char *ends, *ap = strcasestr(line, name);
 147        size_t sz;
 148
 149        if (!ap) {
 150                strbuf_setlen(attr, 0);
 151                return 0;
 152        }
 153        ap += strlen(name);
 154        if (*ap == '"') {
 155                ap++;
 156                ends = "\"";
 157        }
 158        else
 159                ends = "; \t";
 160        sz = strcspn(ap, ends);
 161        strbuf_add(attr, ap, sz);
 162        return 1;
 163}
 164
 165static struct strbuf *content[MAX_BOUNDARIES];
 166
 167static struct strbuf **content_top = content;
 168
 169static void handle_content_type(struct strbuf *line)
 170{
 171        struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
 172        strbuf_init(boundary, line->len);
 173
 174        if (!strcasestr(line->buf, "text/"))
 175                 message_type = TYPE_OTHER;
 176        if (slurp_attr(line->buf, "boundary=", boundary)) {
 177                strbuf_insert(boundary, 0, "--", 2);
 178                if (content_top++ >= &content[MAX_BOUNDARIES]) {
 179                        fprintf(stderr, "Too many boundaries to handle\n");
 180                        exit(1);
 181                }
 182                *content_top = boundary;
 183                boundary = NULL;
 184        }
 185        if (slurp_attr(line->buf, "charset=", &charset))
 186                strbuf_tolower(&charset);
 187
 188        if (boundary) {
 189                strbuf_release(boundary);
 190                free(boundary);
 191        }
 192}
 193
 194static void handle_content_transfer_encoding(const struct strbuf *line)
 195{
 196        if (strcasestr(line->buf, "base64"))
 197                transfer_encoding = TE_BASE64;
 198        else if (strcasestr(line->buf, "quoted-printable"))
 199                transfer_encoding = TE_QP;
 200        else
 201                transfer_encoding = TE_DONTCARE;
 202}
 203
 204static int is_multipart_boundary(const struct strbuf *line)
 205{
 206        return !strbuf_cmp(line, *content_top);
 207}
 208
 209static void cleanup_subject(struct strbuf *subject)
 210{
 211        char *pos;
 212        size_t remove;
 213        while (subject->len) {
 214                switch (*subject->buf) {
 215                case 'r': case 'R':
 216                        if (subject->len <= 3)
 217                                break;
 218                        if (!memcmp(subject->buf + 1, "e:", 2)) {
 219                                strbuf_remove(subject, 0, 3);
 220                                continue;
 221                        }
 222                        break;
 223                case ' ': case '\t': case ':':
 224                        strbuf_remove(subject, 0, 1);
 225                        continue;
 226                case '[':
 227                        if ((pos = strchr(subject->buf, ']'))) {
 228                                remove = pos - subject->buf;
 229                                if (remove <= (subject->len - remove) * 2) {
 230                                        strbuf_remove(subject, 0, remove + 1);
 231                                        continue;
 232                                }
 233                        } else
 234                                strbuf_remove(subject, 0, 1);
 235                        break;
 236                }
 237                strbuf_trim(subject);
 238                return;
 239        }
 240}
 241
 242static void cleanup_space(struct strbuf *sb)
 243{
 244        size_t pos, cnt;
 245        for (pos = 0; pos < sb->len; pos++) {
 246                if (isspace(sb->buf[pos])) {
 247                        sb->buf[pos] = ' ';
 248                        for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
 249                        strbuf_remove(sb, pos + 1, cnt);
 250                }
 251        }
 252}
 253
 254static void decode_header(struct strbuf *line);
 255static const char *header[MAX_HDR_PARSED] = {
 256        "From","Subject","Date",
 257};
 258
 259static inline int cmp_header(const struct strbuf *line, const char *hdr)
 260{
 261        int len = strlen(hdr);
 262        return !strncasecmp(line->buf, hdr, len) && line->len > len &&
 263                        line->buf[len] == ':' && isspace(line->buf[len + 1]);
 264}
 265
 266static int check_header(const struct strbuf *line,
 267                                struct strbuf *hdr_data[], int overwrite)
 268{
 269        int i, ret = 0, len;
 270        struct strbuf sb = STRBUF_INIT;
 271        /* search for the interesting parts */
 272        for (i = 0; header[i]; i++) {
 273                int len = strlen(header[i]);
 274                if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
 275                        /* Unwrap inline B and Q encoding, and optionally
 276                         * normalize the meta information to utf8.
 277                         */
 278                        strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
 279                        decode_header(&sb);
 280                        handle_header(&hdr_data[i], &sb);
 281                        ret = 1;
 282                        goto check_header_out;
 283                }
 284        }
 285
 286        /* Content stuff */
 287        if (cmp_header(line, "Content-Type")) {
 288                len = strlen("Content-Type: ");
 289                strbuf_add(&sb, line->buf + len, line->len - len);
 290                decode_header(&sb);
 291                strbuf_insert(&sb, 0, "Content-Type: ", len);
 292                handle_content_type(&sb);
 293                ret = 1;
 294                goto check_header_out;
 295        }
 296        if (cmp_header(line, "Content-Transfer-Encoding")) {
 297                len = strlen("Content-Transfer-Encoding: ");
 298                strbuf_add(&sb, line->buf + len, line->len - len);
 299                decode_header(&sb);
 300                handle_content_transfer_encoding(&sb);
 301                ret = 1;
 302                goto check_header_out;
 303        }
 304
 305        /* for inbody stuff */
 306        if (!prefixcmp(line->buf, ">From") && isspace(line->buf[5])) {
 307                ret = 1; /* Should this return 0? */
 308                goto check_header_out;
 309        }
 310        if (!prefixcmp(line->buf, "[PATCH]") && isspace(line->buf[7])) {
 311                for (i = 0; header[i]; i++) {
 312                        if (!memcmp("Subject", header[i], 7)) {
 313                                handle_header(&hdr_data[i], line);
 314                                ret = 1;
 315                                goto check_header_out;
 316                        }
 317                }
 318        }
 319
 320check_header_out:
 321        strbuf_release(&sb);
 322        return ret;
 323}
 324
 325static int is_rfc2822_header(const struct strbuf *line)
 326{
 327        /*
 328         * The section that defines the loosest possible
 329         * field name is "3.6.8 Optional fields".
 330         *
 331         * optional-field = field-name ":" unstructured CRLF
 332         * field-name = 1*ftext
 333         * ftext = %d33-57 / %59-126
 334         */
 335        int ch;
 336        char *cp = line->buf;
 337
 338        /* Count mbox From headers as headers */
 339        if (!prefixcmp(cp, "From ") || !prefixcmp(cp, ">From "))
 340                return 1;
 341
 342        while ((ch = *cp++)) {
 343                if (ch == ':')
 344                        return 1;
 345                if ((33 <= ch && ch <= 57) ||
 346                    (59 <= ch && ch <= 126))
 347                        continue;
 348                break;
 349        }
 350        return 0;
 351}
 352
 353static int read_one_header_line(struct strbuf *line, FILE *in)
 354{
 355        /* Get the first part of the line. */
 356        if (strbuf_getline(line, in, '\n'))
 357                return 0;
 358
 359        /*
 360         * Is it an empty line or not a valid rfc2822 header?
 361         * If so, stop here, and return false ("not a header")
 362         */
 363        strbuf_rtrim(line);
 364        if (!line->len || !is_rfc2822_header(line)) {
 365                /* Re-add the newline */
 366                strbuf_addch(line, '\n');
 367                return 0;
 368        }
 369
 370        /*
 371         * Now we need to eat all the continuation lines..
 372         * Yuck, 2822 header "folding"
 373         */
 374        for (;;) {
 375                int peek;
 376                struct strbuf continuation = STRBUF_INIT;
 377
 378                peek = fgetc(in); ungetc(peek, in);
 379                if (peek != ' ' && peek != '\t')
 380                        break;
 381                if (strbuf_getline(&continuation, in, '\n'))
 382                        break;
 383                continuation.buf[0] = '\n';
 384                strbuf_rtrim(&continuation);
 385                strbuf_addbuf(line, &continuation);
 386        }
 387
 388        return 1;
 389}
 390
 391static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
 392{
 393        const char *in = q_seg->buf;
 394        int c;
 395        struct strbuf *out = xmalloc(sizeof(struct strbuf));
 396        strbuf_init(out, q_seg->len);
 397
 398        while ((c = *in++) != 0) {
 399                if (c == '=') {
 400                        int d = *in++;
 401                        if (d == '\n' || !d)
 402                                break; /* drop trailing newline */
 403                        strbuf_addch(out, (hexval(d) << 4) | hexval(*in++));
 404                        continue;
 405                }
 406                if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
 407                        c = 0x20;
 408                strbuf_addch(out, c);
 409        }
 410        return out;
 411}
 412
 413static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
 414{
 415        /* Decode in..ep, possibly in-place to ot */
 416        int c, pos = 0, acc = 0;
 417        const char *in = b_seg->buf;
 418        struct strbuf *out = xmalloc(sizeof(struct strbuf));
 419        strbuf_init(out, b_seg->len);
 420
 421        while ((c = *in++) != 0) {
 422                if (c == '+')
 423                        c = 62;
 424                else if (c == '/')
 425                        c = 63;
 426                else if ('A' <= c && c <= 'Z')
 427                        c -= 'A';
 428                else if ('a' <= c && c <= 'z')
 429                        c -= 'a' - 26;
 430                else if ('0' <= c && c <= '9')
 431                        c -= '0' - 52;
 432                else if (c == '=') {
 433                        /* padding is almost like (c == 0), except we do
 434                         * not output NUL resulting only from it;
 435                         * for now we just trust the data.
 436                         */
 437                        c = 0;
 438                }
 439                else
 440                        continue; /* garbage */
 441                switch (pos++) {
 442                case 0:
 443                        acc = (c << 2);
 444                        break;
 445                case 1:
 446                        strbuf_addch(out, (acc | (c >> 4)));
 447                        acc = (c & 15) << 4;
 448                        break;
 449                case 2:
 450                        strbuf_addch(out, (acc | (c >> 2)));
 451                        acc = (c & 3) << 6;
 452                        break;
 453                case 3:
 454                        strbuf_addch(out, (acc | c));
 455                        acc = pos = 0;
 456                        break;
 457                }
 458        }
 459        return out;
 460}
 461
 462/*
 463 * When there is no known charset, guess.
 464 *
 465 * Right now we assume that if the target is UTF-8 (the default),
 466 * and it already looks like UTF-8 (which includes US-ASCII as its
 467 * subset, of course) then that is what it is and there is nothing
 468 * to do.
 469 *
 470 * Otherwise, we default to assuming it is Latin1 for historical
 471 * reasons.
 472 */
 473static const char *guess_charset(const struct strbuf *line, const char *target_charset)
 474{
 475        if (is_encoding_utf8(target_charset)) {
 476                if (is_utf8(line->buf))
 477                        return NULL;
 478        }
 479        return "latin1";
 480}
 481
 482static void convert_to_utf8(struct strbuf *line, const char *charset)
 483{
 484        char *out;
 485
 486        if (!charset || !*charset) {
 487                charset = guess_charset(line, metainfo_charset);
 488                if (!charset)
 489                        return;
 490        }
 491
 492        if (!strcmp(metainfo_charset, charset))
 493                return;
 494        out = reencode_string(line->buf, metainfo_charset, charset);
 495        if (!out)
 496                die("cannot convert from %s to %s\n",
 497                    charset, metainfo_charset);
 498        strbuf_attach(line, out, strlen(out), strlen(out));
 499}
 500
 501static int decode_header_bq(struct strbuf *it)
 502{
 503        char *in, *ep, *cp;
 504        struct strbuf outbuf = STRBUF_INIT, *dec;
 505        struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
 506        int rfc2047 = 0;
 507
 508        in = it->buf;
 509        while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
 510                int encoding;
 511                strbuf_reset(&charset_q);
 512                strbuf_reset(&piecebuf);
 513                rfc2047 = 1;
 514
 515                if (in != ep) {
 516                        strbuf_add(&outbuf, in, ep - in);
 517                        in = ep;
 518                }
 519                /* E.g.
 520                 * ep : "=?iso-2022-jp?B?GyR...?= foo"
 521                 * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
 522                 */
 523                ep += 2;
 524
 525                if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
 526                        goto decode_header_bq_out;
 527
 528                if (cp + 3 - it->buf > it->len)
 529                        goto decode_header_bq_out;
 530                strbuf_add(&charset_q, ep, cp - ep);
 531                strbuf_tolower(&charset_q);
 532
 533                encoding = cp[1];
 534                if (!encoding || cp[2] != '?')
 535                        goto decode_header_bq_out;
 536                ep = strstr(cp + 3, "?=");
 537                if (!ep)
 538                        goto decode_header_bq_out;
 539                strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
 540                switch (tolower(encoding)) {
 541                default:
 542                        goto decode_header_bq_out;
 543                case 'b':
 544                        dec = decode_b_segment(&piecebuf);
 545                        break;
 546                case 'q':
 547                        dec = decode_q_segment(&piecebuf, 1);
 548                        break;
 549                }
 550                if (metainfo_charset)
 551                        convert_to_utf8(dec, charset_q.buf);
 552
 553                strbuf_addbuf(&outbuf, dec);
 554                strbuf_release(dec);
 555                free(dec);
 556                in = ep + 2;
 557        }
 558        strbuf_addstr(&outbuf, in);
 559        strbuf_reset(it);
 560        strbuf_addbuf(it, &outbuf);
 561decode_header_bq_out:
 562        strbuf_release(&outbuf);
 563        strbuf_release(&charset_q);
 564        strbuf_release(&piecebuf);
 565        return rfc2047;
 566}
 567
 568static void decode_header(struct strbuf *it)
 569{
 570        if (decode_header_bq(it))
 571                return;
 572        /* otherwise "it" is a straight copy of the input.
 573         * This can be binary guck but there is no charset specified.
 574         */
 575        if (metainfo_charset)
 576                convert_to_utf8(it, "");
 577}
 578
 579static void decode_transfer_encoding(struct strbuf *line)
 580{
 581        struct strbuf *ret;
 582
 583        switch (transfer_encoding) {
 584        case TE_QP:
 585                ret = decode_q_segment(line, 0);
 586                break;
 587        case TE_BASE64:
 588                ret = decode_b_segment(line);
 589                break;
 590        case TE_DONTCARE:
 591        default:
 592                return;
 593        }
 594        strbuf_reset(line);
 595        strbuf_addbuf(line, ret);
 596        strbuf_release(ret);
 597        free(ret);
 598}
 599
 600static void handle_filter(struct strbuf *line);
 601
 602static int find_boundary(void)
 603{
 604        while (!strbuf_getline(&line, fin, '\n')) {
 605                if (is_multipart_boundary(&line))
 606                        return 1;
 607        }
 608        return 0;
 609}
 610
 611static int handle_boundary(void)
 612{
 613        struct strbuf newline = STRBUF_INIT;
 614
 615        strbuf_addch(&newline, '\n');
 616again:
 617        if (line.len >= (*content_top)->len + 2 &&
 618            !memcmp(line.buf + (*content_top)->len, "--", 2)) {
 619                /* we hit an end boundary */
 620                /* pop the current boundary off the stack */
 621                strbuf_release(*content_top);
 622                free(*content_top);
 623                *content_top = NULL;
 624
 625                /* technically won't happen as is_multipart_boundary()
 626                   will fail first.  But just in case..
 627                 */
 628                if (content_top-- < content) {
 629                        fprintf(stderr, "Detected mismatched boundaries, "
 630                                        "can't recover\n");
 631                        exit(1);
 632                }
 633                handle_filter(&newline);
 634                strbuf_release(&newline);
 635
 636                /* skip to the next boundary */
 637                if (!find_boundary())
 638                        return 0;
 639                goto again;
 640        }
 641
 642        /* set some defaults */
 643        transfer_encoding = TE_DONTCARE;
 644        strbuf_reset(&charset);
 645        message_type = TYPE_TEXT;
 646
 647        /* slurp in this section's info */
 648        while (read_one_header_line(&line, fin))
 649                check_header(&line, p_hdr_data, 0);
 650
 651        strbuf_release(&newline);
 652        /* eat the blank line after section info */
 653        return (strbuf_getline(&line, fin, '\n') == 0);
 654}
 655
 656static inline int patchbreak(const struct strbuf *line)
 657{
 658        size_t i;
 659
 660        /* Beginning of a "diff -" header? */
 661        if (!prefixcmp(line->buf, "diff -"))
 662                return 1;
 663
 664        /* CVS "Index: " line? */
 665        if (!prefixcmp(line->buf, "Index: "))
 666                return 1;
 667
 668        /*
 669         * "--- <filename>" starts patches without headers
 670         * "---<sp>*" is a manual separator
 671         */
 672        if (line->len < 4)
 673                return 0;
 674
 675        if (!prefixcmp(line->buf, "---")) {
 676                /* space followed by a filename? */
 677                if (line->buf[3] == ' ' && !isspace(line->buf[4]))
 678                        return 1;
 679                /* Just whitespace? */
 680                for (i = 3; i < line->len; i++) {
 681                        unsigned char c = line->buf[i];
 682                        if (c == '\n')
 683                                return 1;
 684                        if (!isspace(c))
 685                                break;
 686                }
 687                return 0;
 688        }
 689        return 0;
 690}
 691
 692static int handle_commit_msg(struct strbuf *line)
 693{
 694        static int still_looking = 1;
 695
 696        if (!cmitmsg)
 697                return 0;
 698
 699        if (still_looking) {
 700                strbuf_ltrim(line);
 701                if (!line->len)
 702                        return 0;
 703                if ((still_looking = check_header(line, s_hdr_data, 0)) != 0)
 704                        return 0;
 705        }
 706
 707        /* normalize the log message to UTF-8. */
 708        if (metainfo_charset)
 709                convert_to_utf8(line, charset.buf);
 710
 711        if (patchbreak(line)) {
 712                fclose(cmitmsg);
 713                cmitmsg = NULL;
 714                return 1;
 715        }
 716
 717        fputs(line->buf, cmitmsg);
 718        return 0;
 719}
 720
 721static void handle_patch(const struct strbuf *line)
 722{
 723        fwrite(line->buf, 1, line->len, patchfile);
 724        patch_lines++;
 725}
 726
 727static void handle_filter(struct strbuf *line)
 728{
 729        static int filter = 0;
 730
 731        /* filter tells us which part we left off on */
 732        switch (filter) {
 733        case 0:
 734                if (!handle_commit_msg(line))
 735                        break;
 736                filter++;
 737        case 1:
 738                handle_patch(line);
 739                break;
 740        }
 741}
 742
 743static void handle_body(void)
 744{
 745        int len = 0;
 746        struct strbuf prev = STRBUF_INIT;
 747
 748        /* Skip up to the first boundary */
 749        if (*content_top) {
 750                if (!find_boundary())
 751                        goto handle_body_out;
 752        }
 753
 754        do {
 755                strbuf_setlen(&line, line.len + len);
 756
 757                /* process any boundary lines */
 758                if (*content_top && is_multipart_boundary(&line)) {
 759                        /* flush any leftover */
 760                        if (line.len)
 761                                handle_filter(&line);
 762
 763                        if (!handle_boundary())
 764                                goto handle_body_out;
 765                }
 766
 767                /* Unwrap transfer encoding */
 768                decode_transfer_encoding(&line);
 769
 770                switch (transfer_encoding) {
 771                case TE_BASE64:
 772                case TE_QP:
 773                {
 774                        struct strbuf **lines, **it, *sb;
 775
 776                        /* Prepend any previous partial lines */
 777                        strbuf_insert(&line, 0, prev.buf, prev.len);
 778                        strbuf_reset(&prev);
 779
 780                        /* binary data most likely doesn't have newlines */
 781                        if (message_type != TYPE_TEXT) {
 782                                handle_filter(&line);
 783                                break;
 784                        }
 785                        /*
 786                         * This is a decoded line that may contain
 787                         * multiple new lines.  Pass only one chunk
 788                         * at a time to handle_filter()
 789                         */
 790                        lines = strbuf_split(&line, '\n');
 791                        for (it = lines; (sb = *it); it++) {
 792                                if (*(it + 1) == NULL) /* The last line */
 793                                        if (sb->buf[sb->len - 1] != '\n') {
 794                                                /* Partial line, save it for later. */
 795                                                strbuf_addbuf(&prev, sb);
 796                                                break;
 797                                        }
 798                                handle_filter(sb);
 799                        }
 800                        /*
 801                         * The partial chunk is saved in "prev" and will be
 802                         * appended by the next iteration of read_line_with_nul().
 803                         */
 804                        strbuf_list_free(lines);
 805                        break;
 806                }
 807                default:
 808                        handle_filter(&line);
 809                }
 810
 811                strbuf_reset(&line);
 812                if (strbuf_avail(&line) < 100)
 813                        strbuf_grow(&line, 100);
 814        } while ((len = read_line_with_nul(line.buf, strbuf_avail(&line), fin)));
 815
 816handle_body_out:
 817        strbuf_release(&prev);
 818}
 819
 820static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
 821{
 822        const char *sp = data->buf;
 823        while (1) {
 824                char *ep = strchr(sp, '\n');
 825                int len;
 826                if (!ep)
 827                        len = strlen(sp);
 828                else
 829                        len = ep - sp;
 830                fprintf(fout, "%s: %.*s\n", hdr, len, sp);
 831                if (!ep)
 832                        break;
 833                sp = ep + 1;
 834        }
 835}
 836
 837static void handle_info(void)
 838{
 839        struct strbuf *hdr;
 840        int i;
 841
 842        for (i = 0; header[i]; i++) {
 843                /* only print inbody headers if we output a patch file */
 844                if (patch_lines && s_hdr_data[i])
 845                        hdr = s_hdr_data[i];
 846                else if (p_hdr_data[i])
 847                        hdr = p_hdr_data[i];
 848                else
 849                        continue;
 850
 851                if (!memcmp(header[i], "Subject", 7)) {
 852                        if (!keep_subject) {
 853                                cleanup_subject(hdr);
 854                                cleanup_space(hdr);
 855                        }
 856                        output_header_lines(fout, "Subject", hdr);
 857                } else if (!memcmp(header[i], "From", 4)) {
 858                        handle_from(hdr);
 859                        fprintf(fout, "Author: %s\n", name.buf);
 860                        fprintf(fout, "Email: %s\n", email.buf);
 861                } else {
 862                        cleanup_space(hdr);
 863                        fprintf(fout, "%s: %s\n", header[i], hdr->buf);
 864                }
 865        }
 866        fprintf(fout, "\n");
 867}
 868
 869static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
 870                    const char *msg, const char *patch)
 871{
 872        int peek;
 873        keep_subject = ks;
 874        metainfo_charset = encoding;
 875        fin = in;
 876        fout = out;
 877
 878        cmitmsg = fopen(msg, "w");
 879        if (!cmitmsg) {
 880                perror(msg);
 881                return -1;
 882        }
 883        patchfile = fopen(patch, "w");
 884        if (!patchfile) {
 885                perror(patch);
 886                fclose(cmitmsg);
 887                return -1;
 888        }
 889
 890        p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*p_hdr_data));
 891        s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*s_hdr_data));
 892
 893        do {
 894                peek = fgetc(in);
 895        } while (isspace(peek));
 896        ungetc(peek, in);
 897
 898        /* process the email header */
 899        while (read_one_header_line(&line, fin))
 900                check_header(&line, p_hdr_data, 1);
 901
 902        handle_body();
 903        handle_info();
 904
 905        return 0;
 906}
 907
 908static const char mailinfo_usage[] =
 909        "git mailinfo [-k] [-u | --encoding=<encoding> | -n] msg patch <mail >info";
 910
 911int cmd_mailinfo(int argc, const char **argv, const char *prefix)
 912{
 913        const char *def_charset;
 914
 915        /* NEEDSWORK: might want to do the optional .git/ directory
 916         * discovery
 917         */
 918        git_config(git_default_config, NULL);
 919
 920        def_charset = (git_commit_encoding ? git_commit_encoding : "utf-8");
 921        metainfo_charset = def_charset;
 922
 923        while (1 < argc && argv[1][0] == '-') {
 924                if (!strcmp(argv[1], "-k"))
 925                        keep_subject = 1;
 926                else if (!strcmp(argv[1], "-u"))
 927                        metainfo_charset = def_charset;
 928                else if (!strcmp(argv[1], "-n"))
 929                        metainfo_charset = NULL;
 930                else if (!prefixcmp(argv[1], "--encoding="))
 931                        metainfo_charset = argv[1] + 11;
 932                else
 933                        usage(mailinfo_usage);
 934                argc--; argv++;
 935        }
 936
 937        if (argc != 3)
 938                usage(mailinfo_usage);
 939
 940        return !!mailinfo(stdin, stdout, keep_subject, metainfo_charset, argv[1], argv[2]);
 941}