0ccd490082455590f11e44cda5ce199dd56b9ed9
   1/*
   2 * Another stupid program, this one parsing the headers of an
   3 * email to figure out authorship and subject
   4 */
   5#define _GNU_SOURCE
   6#include <stdio.h>
   7#include <stdlib.h>
   8#include <string.h>
   9#include <ctype.h>
  10#ifndef NO_ICONV
  11#include <iconv.h>
  12#endif
  13#include "git-compat-util.h"
  14#include "cache.h"
  15
  16static FILE *cmitmsg, *patchfile;
  17
  18static int keep_subject = 0;
  19static char *metainfo_charset = NULL;
  20static char line[1000];
  21static char date[1000];
  22static char name[1000];
  23static char email[1000];
  24static char subject[1000];
  25
  26static enum  {
  27        TE_DONTCARE, TE_QP, TE_BASE64,
  28} transfer_encoding;
  29static char charset[256];
  30
  31static char multipart_boundary[1000];
  32static int multipart_boundary_len;
  33static int patch_lines = 0;
  34
  35static char *sanity_check(char *name, char *email)
  36{
  37        int len = strlen(name);
  38        if (len < 3 || len > 60)
  39                return email;
  40        if (strchr(name, '@') || strchr(name, '<') || strchr(name, '>'))
  41                return email;
  42        return name;
  43}
  44
  45static int bogus_from(char *line)
  46{
  47        /* John Doe <johndoe> */
  48        char *bra, *ket, *dst, *cp;
  49
  50        /* This is fallback, so do not bother if we already have an
  51         * e-mail address.
  52         */ 
  53        if (*email)
  54                return 0;
  55
  56        bra = strchr(line, '<');
  57        if (!bra)
  58                return 0;
  59        ket = strchr(bra, '>');
  60        if (!ket)
  61                return 0;
  62
  63        for (dst = email, cp = bra+1; cp < ket; )
  64                *dst++ = *cp++;
  65        *dst = 0;
  66        for (cp = line; isspace(*cp); cp++)
  67                ;
  68        for (bra--; isspace(*bra); bra--)
  69                *bra = 0;
  70        cp = sanity_check(cp, email);
  71        strcpy(name, cp);
  72        return 1;
  73}
  74
  75static int handle_from(char *in_line)
  76{
  77        char line[1000];
  78        char *at;
  79        char *dst;
  80
  81        strcpy(line, in_line);
  82        at = strchr(line, '@');
  83        if (!at)
  84                return bogus_from(line);
  85
  86        /*
  87         * If we already have one email, don't take any confusing lines
  88         */
  89        if (*email && strchr(at+1, '@'))
  90                return 0;
  91
  92        /* Pick up the string around '@', possibly delimited with <>
  93         * pair; that is the email part.  White them out while copying.
  94         */
  95        while (at > line) {
  96                char c = at[-1];
  97                if (isspace(c))
  98                        break;
  99                if (c == '<') {
 100                        at[-1] = ' ';
 101                        break;
 102                }
 103                at--;
 104        }
 105        dst = email;
 106        for (;;) {
 107                unsigned char c = *at;
 108                if (!c || c == '>' || isspace(c)) {
 109                        if (c == '>')
 110                                *at = ' ';
 111                        break;
 112                }
 113                *at++ = ' ';
 114                *dst++ = c;
 115        }
 116        *dst++ = 0;
 117
 118        /* The remainder is name.  It could be "John Doe <john.doe@xz>"
 119         * or "john.doe@xz (John Doe)", but we have whited out the
 120         * email part, so trim from both ends, possibly removing
 121         * the () pair at the end.
 122         */
 123        at = line + strlen(line);
 124        while (at > line) {
 125                unsigned char c = *--at;
 126                if (!isspace(c)) {
 127                        at[(c == ')') ? 0 : 1] = 0;
 128                        break;
 129                }
 130        }
 131
 132        at = line;
 133        for (;;) {
 134                unsigned char c = *at;
 135                if (!c || !isspace(c)) {
 136                        if (c == '(')
 137                                at++;
 138                        break;
 139                }
 140                at++;
 141        }
 142        at = sanity_check(at, email);
 143        strcpy(name, at);
 144        return 1;
 145}
 146
 147static int handle_date(char *line)
 148{
 149        strcpy(date, line);
 150        return 0;
 151}
 152
 153static int handle_subject(char *line)
 154{
 155        strcpy(subject, line);
 156        return 0;
 157}
 158
 159/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt
 160 * to have enough heuristics to grok MIME encoded patches often found
 161 * on our mailing lists.  For example, we do not even treat header lines
 162 * case insensitively.
 163 */
 164
 165static int slurp_attr(const char *line, const char *name, char *attr)
 166{
 167        char *ends, *ap = strcasestr(line, name);
 168        size_t sz;
 169
 170        if (!ap) {
 171                *attr = 0;
 172                return 0;
 173        }
 174        ap += strlen(name);
 175        if (*ap == '"') {
 176                ap++;
 177                ends = "\"";
 178        }
 179        else
 180                ends = "; \t";
 181        sz = strcspn(ap, ends);
 182        memcpy(attr, ap, sz);
 183        attr[sz] = 0;
 184        return 1;
 185}
 186
 187static int handle_subcontent_type(char *line)
 188{
 189        /* We do not want to mess with boundary.  Note that we do not
 190         * handle nested multipart.
 191         */
 192        if (strcasestr(line, "boundary=")) {
 193                fprintf(stderr, "Not handling nested multipart message.\n");
 194                exit(1);
 195        }
 196        slurp_attr(line, "charset=", charset);
 197        if (*charset) {
 198                int i, c;
 199                for (i = 0; (c = charset[i]) != 0; i++)
 200                        charset[i] = tolower(c);
 201        }
 202        return 0;
 203}
 204
 205static int handle_content_type(char *line)
 206{
 207        *multipart_boundary = 0;
 208        if (slurp_attr(line, "boundary=", multipart_boundary + 2)) {
 209                memcpy(multipart_boundary, "--", 2);
 210                multipart_boundary_len = strlen(multipart_boundary);
 211        }
 212        slurp_attr(line, "charset=", charset);
 213        return 0;
 214}
 215
 216static int handle_content_transfer_encoding(char *line)
 217{
 218        if (strcasestr(line, "base64"))
 219                transfer_encoding = TE_BASE64;
 220        else if (strcasestr(line, "quoted-printable"))
 221                transfer_encoding = TE_QP;
 222        else
 223                transfer_encoding = TE_DONTCARE;
 224        return 0;
 225}
 226
 227static int is_multipart_boundary(const char *line)
 228{
 229        return (!memcmp(line, multipart_boundary, multipart_boundary_len));
 230}
 231
 232static int eatspace(char *line)
 233{
 234        int len = strlen(line);
 235        while (len > 0 && isspace(line[len-1]))
 236                line[--len] = 0;
 237        return len;
 238}
 239
 240#define SEEN_FROM 01
 241#define SEEN_DATE 02
 242#define SEEN_SUBJECT 04
 243#define SEEN_BOGUS_UNIX_FROM 010
 244#define SEEN_PREFIX  020
 245
 246/* First lines of body can have From:, Date:, and Subject: */
 247static void handle_inbody_header(int *seen, char *line)
 248{
 249        if (*seen & SEEN_PREFIX)
 250                return;
 251        if (!memcmp(">From", line, 5) && isspace(line[5])) {
 252                if (!(*seen & SEEN_BOGUS_UNIX_FROM)) {
 253                        *seen |= SEEN_BOGUS_UNIX_FROM;
 254                        return;
 255                }
 256        }
 257        if (!memcmp("From:", line, 5) && isspace(line[5])) {
 258                if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
 259                        *seen |= SEEN_FROM;
 260                        return;
 261                }
 262        }
 263        if (!memcmp("Date:", line, 5) && isspace(line[5])) {
 264                if (!(*seen & SEEN_DATE)) {
 265                        handle_date(line+6);
 266                        *seen |= SEEN_DATE;
 267                        return;
 268                }
 269        }
 270        if (!memcmp("Subject:", line, 8) && isspace(line[8])) {
 271                if (!(*seen & SEEN_SUBJECT)) {
 272                        handle_subject(line+9);
 273                        *seen |= SEEN_SUBJECT;
 274                        return;
 275                }
 276        }
 277        if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
 278                if (!(*seen & SEEN_SUBJECT)) {
 279                        handle_subject(line);
 280                        *seen |= SEEN_SUBJECT;
 281                        return;
 282                }
 283        }
 284        *seen |= SEEN_PREFIX;
 285}
 286
 287static char *cleanup_subject(char *subject)
 288{
 289        if (keep_subject)
 290                return subject;
 291        for (;;) {
 292                char *p;
 293                int len, remove;
 294                switch (*subject) {
 295                case 'r': case 'R':
 296                        if (!memcmp("e:", subject+1, 2)) {
 297                                subject +=3;
 298                                continue;
 299                        }
 300                        break;
 301                case ' ': case '\t': case ':':
 302                        subject++;
 303                        continue;
 304
 305                case '[':
 306                        p = strchr(subject, ']');
 307                        if (!p) {
 308                                subject++;
 309                                continue;
 310                        }
 311                        len = strlen(p);
 312                        remove = p - subject;
 313                        if (remove <= len *2) {
 314                                subject = p+1;
 315                                continue;
 316                        }       
 317                        break;
 318                }
 319                return subject;
 320        }
 321}                       
 322
 323static void cleanup_space(char *buf)
 324{
 325        unsigned char c;
 326        while ((c = *buf) != 0) {
 327                buf++;
 328                if (isspace(c)) {
 329                        buf[-1] = ' ';
 330                        c = *buf;
 331                        while (isspace(c)) {
 332                                int len = strlen(buf);
 333                                memmove(buf, buf+1, len);
 334                                c = *buf;
 335                        }
 336                }
 337        }
 338}
 339
 340static void decode_header_bq(char *it);
 341typedef int (*header_fn_t)(char *);
 342struct header_def {
 343        const char *name;
 344        header_fn_t func;
 345        int namelen;
 346};
 347
 348static void check_header(char *line, struct header_def *header)
 349{
 350        int i;
 351
 352        if (header[0].namelen <= 0) {
 353                for (i = 0; header[i].name; i++)
 354                        header[i].namelen = strlen(header[i].name);
 355        }
 356        for (i = 0; header[i].name; i++) {
 357                int len = header[i].namelen;
 358                if (!strncasecmp(line, header[i].name, len) &&
 359                    line[len] == ':' && isspace(line[len + 1])) {
 360                        /* Unwrap inline B and Q encoding, and optionally
 361                         * normalize the meta information to utf8.
 362                         */
 363                        decode_header_bq(line + len + 2);
 364                        header[i].func(line + len + 2);
 365                        break;
 366                }
 367        }
 368}
 369
 370static void check_subheader_line(char *line)
 371{
 372        static struct header_def header[] = {
 373                { "Content-Type", handle_subcontent_type },
 374                { "Content-Transfer-Encoding",
 375                  handle_content_transfer_encoding },
 376                { NULL },
 377        };
 378        check_header(line, header);
 379}
 380static void check_header_line(char *line)
 381{
 382        static struct header_def header[] = {
 383                { "From", handle_from },
 384                { "Date", handle_date },
 385                { "Subject", handle_subject },
 386                { "Content-Type", handle_content_type },
 387                { "Content-Transfer-Encoding",
 388                  handle_content_transfer_encoding },
 389                { NULL },
 390        };
 391        check_header(line, header);
 392}
 393
 394static int is_rfc2822_header(char *line)
 395{
 396        /*
 397         * The section that defines the loosest possible
 398         * field name is "3.6.8 Optional fields".
 399         *
 400         * optional-field = field-name ":" unstructured CRLF
 401         * field-name = 1*ftext
 402         * ftext = %d33-57 / %59-126
 403         */
 404        int ch;
 405        char *cp = line;
 406        while ((ch = *cp++)) {
 407                if (ch == ':')
 408                        return cp != line;
 409                if ((33 <= ch && ch <= 57) ||
 410                    (59 <= ch && ch <= 126))
 411                        continue;
 412                break;
 413        }
 414        return 0;
 415}
 416
 417static int read_one_header_line(char *line, int sz, FILE *in)
 418{
 419        int ofs = 0;
 420        while (ofs < sz) {
 421                int peek, len;
 422                if (fgets(line + ofs, sz - ofs, in) == NULL)
 423                        break;
 424                len = eatspace(line + ofs);
 425                if (len == 0)
 426                        break;
 427                if (!is_rfc2822_header(line)) {
 428                        /* Re-add the newline */
 429                        line[ofs + len] = '\n';
 430                        line[ofs + len + 1] = '\0';
 431                        break;
 432                }
 433                ofs += len;
 434                /* Yuck, 2822 header "folding" */
 435                peek = fgetc(in); ungetc(peek, in);
 436                if (peek != ' ' && peek != '\t')
 437                        break;
 438        }
 439        /* Count mbox From headers as headers */
 440        if (!ofs && !memcmp(line, "From ", 5))
 441                ofs = 1;
 442        return ofs;
 443}
 444
 445static unsigned hexval(int c)
 446{
 447        if (c >= '0' && c <= '9')
 448                return c - '0';
 449        if (c >= 'a' && c <= 'f')
 450                return c - 'a' + 10;
 451        if (c >= 'A' && c <= 'F')
 452                return c - 'A' + 10;
 453        return ~0;
 454}
 455
 456static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047)
 457{
 458        int c;
 459        while ((c = *in++) != 0 && (in <= ep)) {
 460                if (c == '=') {
 461                        int d = *in++;
 462                        if (d == '\n' || !d)
 463                                break; /* drop trailing newline */
 464                        *ot++ = ((hexval(d) << 4) | hexval(*in++));
 465                        continue;
 466                }
 467                if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
 468                        c = 0x20;
 469                *ot++ = c;
 470        }
 471        *ot = 0;
 472        return 0;
 473}
 474
 475static int decode_b_segment(char *in, char *ot, char *ep)
 476{
 477        /* Decode in..ep, possibly in-place to ot */
 478        int c, pos = 0, acc = 0;
 479
 480        while ((c = *in++) != 0 && (in <= ep)) {
 481                if (c == '+')
 482                        c = 62;
 483                else if (c == '/')
 484                        c = 63;
 485                else if ('A' <= c && c <= 'Z')
 486                        c -= 'A';
 487                else if ('a' <= c && c <= 'z')
 488                        c -= 'a' - 26;
 489                else if ('0' <= c && c <= '9')
 490                        c -= '0' - 52;
 491                else if (c == '=') {
 492                        /* padding is almost like (c == 0), except we do
 493                         * not output NUL resulting only from it;
 494                         * for now we just trust the data.
 495                         */
 496                        c = 0;
 497                }
 498                else
 499                        continue; /* garbage */
 500                switch (pos++) {
 501                case 0:
 502                        acc = (c << 2);
 503                        break;
 504                case 1:
 505                        *ot++ = (acc | (c >> 4));
 506                        acc = (c & 15) << 4;
 507                        break;
 508                case 2:
 509                        *ot++ = (acc | (c >> 2));
 510                        acc = (c & 3) << 6;
 511                        break;
 512                case 3:
 513                        *ot++ = (acc | c);
 514                        acc = pos = 0;
 515                        break;
 516                }
 517        }
 518        *ot = 0;
 519        return 0;
 520}
 521
 522static void convert_to_utf8(char *line, char *charset)
 523{
 524#ifndef NO_ICONV
 525        char *in, *out;
 526        size_t insize, outsize, nrc;
 527        char outbuf[4096]; /* cheat */
 528        static char latin_one[] = "latin1";
 529        char *input_charset = *charset ? charset : latin_one;
 530        iconv_t conv = iconv_open(metainfo_charset, input_charset);
 531
 532        if (conv == (iconv_t) -1) {
 533                static int warned_latin1_once = 0;
 534                if (input_charset != latin_one) {
 535                        fprintf(stderr, "cannot convert from %s to %s\n",
 536                                input_charset, metainfo_charset);
 537                        *charset = 0;
 538                }
 539                else if (!warned_latin1_once) {
 540                        warned_latin1_once = 1;
 541                        fprintf(stderr, "tried to convert from %s to %s, "
 542                                "but your iconv does not work with it.\n",
 543                                input_charset, metainfo_charset);
 544                }
 545                return;
 546        }
 547        in = line;
 548        insize = strlen(in);
 549        out = outbuf;
 550        outsize = sizeof(outbuf);
 551        nrc = iconv(conv, &in, &insize, &out, &outsize);
 552        iconv_close(conv);
 553        if (nrc == (size_t) -1)
 554                return;
 555        *out = 0;
 556        strcpy(line, outbuf);
 557#endif
 558}
 559
 560static void decode_header_bq(char *it)
 561{
 562        char *in, *out, *ep, *cp, *sp;
 563        char outbuf[1000];
 564
 565        in = it;
 566        out = outbuf;
 567        while ((ep = strstr(in, "=?")) != NULL) {
 568                int sz, encoding;
 569                char charset_q[256], piecebuf[256];
 570                if (in != ep) {
 571                        sz = ep - in;
 572                        memcpy(out, in, sz);
 573                        out += sz;
 574                        in += sz;
 575                }
 576                /* E.g.
 577                 * ep : "=?iso-2022-jp?B?GyR...?= foo"
 578                 * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
 579                 */
 580                ep += 2;
 581                cp = strchr(ep, '?');
 582                if (!cp)
 583                        return; /* no munging */
 584                for (sp = ep; sp < cp; sp++)
 585                        charset_q[sp - ep] = tolower(*sp);
 586                charset_q[cp - ep] = 0;
 587                encoding = cp[1];
 588                if (!encoding || cp[2] != '?')
 589                        return; /* no munging */
 590                ep = strstr(cp + 3, "?=");
 591                if (!ep)
 592                        return; /* no munging */
 593                switch (tolower(encoding)) {
 594                default:
 595                        return; /* no munging */
 596                case 'b':
 597                        sz = decode_b_segment(cp + 3, piecebuf, ep);
 598                        break;
 599                case 'q':
 600                        sz = decode_q_segment(cp + 3, piecebuf, ep, 1);
 601                        break;
 602                }
 603                if (sz < 0)
 604                        return;
 605                if (metainfo_charset)
 606                        convert_to_utf8(piecebuf, charset_q);
 607                strcpy(out, piecebuf);
 608                out += strlen(out);
 609                in = ep + 2;
 610        }
 611        strcpy(out, in);
 612        strcpy(it, outbuf);
 613}
 614
 615static void decode_transfer_encoding(char *line)
 616{
 617        char *ep;
 618
 619        switch (transfer_encoding) {
 620        case TE_QP:
 621                ep = line + strlen(line);
 622                decode_q_segment(line, line, ep, 0);
 623                break;
 624        case TE_BASE64:
 625                ep = line + strlen(line);
 626                decode_b_segment(line, line, ep);
 627                break;
 628        case TE_DONTCARE:
 629                break;
 630        }
 631}
 632
 633static void handle_info(void)
 634{
 635        char *sub;
 636
 637        sub = cleanup_subject(subject);
 638        cleanup_space(name);
 639        cleanup_space(date);
 640        cleanup_space(email);
 641        cleanup_space(sub);
 642
 643        printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n",
 644               name, email, sub, date);
 645}
 646
 647/* We are inside message body and have read line[] already.
 648 * Spit out the commit log.
 649 */
 650static int handle_commit_msg(int *seen)
 651{
 652        if (!cmitmsg)
 653                return 0;
 654        do {
 655                if (!memcmp("diff -", line, 6) ||
 656                    !memcmp("---", line, 3) ||
 657                    !memcmp("Index: ", line, 7))
 658                        break;
 659                if ((multipart_boundary[0] && is_multipart_boundary(line))) {
 660                        /* We come here when the first part had only
 661                         * the commit message without any patch.  We
 662                         * pretend we have not seen this line yet, and
 663                         * go back to the loop.
 664                         */
 665                        return 1;
 666                }
 667
 668                /* Unwrap transfer encoding and optionally
 669                 * normalize the log message to UTF-8.
 670                 */
 671                decode_transfer_encoding(line);
 672                if (metainfo_charset)
 673                        convert_to_utf8(line, charset);
 674
 675                handle_inbody_header(seen, line);
 676                if (!(*seen & SEEN_PREFIX))
 677                        continue;
 678
 679                fputs(line, cmitmsg);
 680        } while (fgets(line, sizeof(line), stdin) != NULL);
 681        fclose(cmitmsg);
 682        cmitmsg = NULL;
 683        return 0;
 684}
 685
 686/* We have done the commit message and have the first
 687 * line of the patch in line[].
 688 */
 689static void handle_patch(void)
 690{
 691        do {
 692                if (multipart_boundary[0] && is_multipart_boundary(line))
 693                        break;
 694                /* Only unwrap transfer encoding but otherwise do not
 695                 * do anything.  We do *NOT* want UTF-8 conversion
 696                 * here; we are dealing with the user payload.
 697                 */
 698                decode_transfer_encoding(line);
 699                fputs(line, patchfile);
 700                patch_lines++;
 701        } while (fgets(line, sizeof(line), stdin) != NULL);
 702}
 703
 704/* multipart boundary and transfer encoding are set up for us, and we
 705 * are at the end of the sub header.  do equivalent of handle_body up
 706 * to the next boundary without closing patchfile --- we will expect
 707 * that the first part to contain commit message and a patch, and
 708 * handle other parts as pure patches.
 709 */
 710static int handle_multipart_one_part(int *seen)
 711{
 712        int n = 0;
 713
 714        while (fgets(line, sizeof(line), stdin) != NULL) {
 715        again:
 716                n++;
 717                if (is_multipart_boundary(line))
 718                        break;
 719                if (handle_commit_msg(seen))
 720                        goto again;
 721                handle_patch();
 722                break;
 723        }
 724        if (n == 0)
 725                return -1;
 726        return 0;
 727}
 728
 729static void handle_multipart_body(void)
 730{
 731        int seen = 0;
 732        int part_num = 0;
 733
 734        /* Skip up to the first boundary */
 735        while (fgets(line, sizeof(line), stdin) != NULL)
 736                if (is_multipart_boundary(line)) {
 737                        part_num = 1;
 738                        break;
 739                }
 740        if (!part_num)
 741                return;
 742        /* We are on boundary line.  Start slurping the subhead. */
 743        while (1) {
 744                int hdr = read_one_header_line(line, sizeof(line), stdin);
 745                if (!hdr) {
 746                        if (handle_multipart_one_part(&seen) < 0)
 747                                return;
 748                        /* Reset per part headers */
 749                        transfer_encoding = TE_DONTCARE;
 750                        charset[0] = 0;
 751                }
 752                else
 753                        check_subheader_line(line);
 754        }
 755        fclose(patchfile);
 756        if (!patch_lines) {
 757                fprintf(stderr, "No patch found\n");
 758                exit(1);
 759        }
 760}
 761
 762/* Non multipart message */
 763static void handle_body(void)
 764{
 765        int seen = 0;
 766
 767        if (line[0] || fgets(line, sizeof(line), stdin) != NULL) {
 768                handle_commit_msg(&seen);
 769                handle_patch();
 770        }
 771        fclose(patchfile);
 772        if (!patch_lines) {
 773                fprintf(stderr, "No patch found\n");
 774                exit(1);
 775        }
 776}
 777
 778static const char mailinfo_usage[] =
 779        "git-mailinfo [-k] [-u | --encoding=<encoding>] msg patch <mail >info";
 780
 781int main(int argc, char **argv)
 782{
 783        /* NEEDSWORK: might want to do the optional .git/ directory
 784         * discovery
 785         */
 786        git_config(git_default_config);
 787
 788        while (1 < argc && argv[1][0] == '-') {
 789                if (!strcmp(argv[1], "-k"))
 790                        keep_subject = 1;
 791                else if (!strcmp(argv[1], "-u"))
 792                        metainfo_charset = git_commit_encoding;
 793                else if (!strncmp(argv[1], "--encoding=", 11))
 794                        metainfo_charset = argv[1] + 11;
 795                else
 796                        usage(mailinfo_usage);
 797                argc--; argv++;
 798        }
 799
 800        if (argc != 3)
 801                usage(mailinfo_usage);
 802        cmitmsg = fopen(argv[1], "w");
 803        if (!cmitmsg) {
 804                perror(argv[1]);
 805                exit(1);
 806        }
 807        patchfile = fopen(argv[2], "w");
 808        if (!patchfile) {
 809                perror(argv[2]);
 810                exit(1);
 811        }
 812        while (1) {
 813                int hdr = read_one_header_line(line, sizeof(line), stdin);
 814                if (!hdr) {
 815                        if (multipart_boundary[0])
 816                                handle_multipart_body();
 817                        else
 818                                handle_body();
 819                        handle_info();
 820                        break;
 821                }
 822                check_header_line(line);
 823        }
 824        return 0;
 825}