Merge branch 'eb/mailinfo' into next
[gitweb.git] / mailinfo.c
index ff2d4d403826b035a7bb123dc74a50af9a76e353..5b6c2157ede415e019099098f2d0dc522b1e7a27 100644 (file)
@@ -7,7 +7,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
+#ifndef NO_ICONV
 #include <iconv.h>
+#endif
 #include "git-compat-util.h"
 #include "cache.h"
 
@@ -70,11 +72,14 @@ static int bogus_from(char *line)
        return 1;
 }
 
-static int handle_from(char *line)
+static int handle_from(char *in_line)
 {
-       char *at = strchr(line, '@');
+       char line[1000];
+       char *at;
        char *dst;
 
+       strcpy(line, in_line);
+       at = strchr(line, '@');
        if (!at)
                return bogus_from(line);
 
@@ -235,38 +240,46 @@ static int eatspace(char *line)
 #define SEEN_FROM 01
 #define SEEN_DATE 02
 #define SEEN_SUBJECT 04
+#define SEEN_BOGUS_UNIX_FROM 010
+#define SEEN_PREFIX  020
 
 /* First lines of body can have From:, Date:, and Subject: */
-static int handle_inbody_header(int *seen, char *line)
+static void handle_inbody_header(int *seen, char *line)
 {
+       if (!memcmp(">From", line, 5) && isspace(line[5])) {
+               if (!(*seen & SEEN_BOGUS_UNIX_FROM)) {
+                       *seen |= SEEN_BOGUS_UNIX_FROM;
+                       return;
+               }
+       }
        if (!memcmp("From:", line, 5) && isspace(line[5])) {
                if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
                        *seen |= SEEN_FROM;
-                       return 1;
+                       return;
                }
        }
        if (!memcmp("Date:", line, 5) && isspace(line[5])) {
                if (!(*seen & SEEN_DATE)) {
                        handle_date(line+6);
                        *seen |= SEEN_DATE;
-                       return 1;
+                       return;
                }
        }
        if (!memcmp("Subject:", line, 8) && isspace(line[8])) {
                if (!(*seen & SEEN_SUBJECT)) {
                        handle_subject(line+9);
                        *seen |= SEEN_SUBJECT;
-                       return 1;
+                       return;
                }
        }
        if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
                if (!(*seen & SEEN_SUBJECT)) {
                        handle_subject(line);
                        *seen |= SEEN_SUBJECT;
-                       return 1;
+                       return;
                }
        }
-       return 0;
+       *seen |= SEEN_PREFIX;
 }
 
 static char *cleanup_subject(char *subject)
@@ -322,6 +335,7 @@ static void cleanup_space(char *buf)
        }
 }
 
+static void decode_header_bq(char *it);
 typedef int (*header_fn_t)(char *);
 struct header_def {
        const char *name;
@@ -329,7 +343,7 @@ struct header_def {
        int namelen;
 };
 
-static void check_header(char *line, int len, struct header_def *header)
+static void check_header(char *line, struct header_def *header)
 {
        int i;
 
@@ -341,13 +355,17 @@ static void check_header(char *line, int len, struct header_def *header)
                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, int len)
+static void check_subheader_line(char *line)
 {
        static struct header_def header[] = {
                { "Content-Type", handle_subcontent_type },
@@ -355,9 +373,9 @@ static void check_subheader_line(char *line, int len)
                  handle_content_transfer_encoding },
                { NULL },
        };
-       check_header(line, len, header);
+       check_header(line, header);
 }
-static void check_header_line(char *line, int len)
+static void check_header_line(char *line)
 {
        static struct header_def header[] = {
                { "From", handle_from },
@@ -368,7 +386,30 @@ static void check_header_line(char *line, int len)
                  handle_content_transfer_encoding },
                { NULL },
        };
-       check_header(line, len, header);
+       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)
@@ -377,18 +418,25 @@ static int read_one_header_line(char *line, int sz, FILE *in)
        while (ofs < sz) {
                int peek, len;
                if (fgets(line + ofs, sz - ofs, in) == NULL)
-                       return ofs;
+                       break;
                len = eatspace(line + ofs);
                if (len == 0)
-                       return ofs;
-               peek = fgetc(in); ungetc(peek, in);
-               if (peek == ' ' || peek == '\t') {
-                       /* Yuck, 2822 header "folding" */
-                       ofs += len;
-                       continue;
+                       break;
+               if (!is_rfc2822_header(line)) {
+                       /* Re-add the newline */
+                       line[ofs + len] = '\n';
+                       line[ofs + len + 1] = '\0';
+                       break;
                }
-               return ofs + len;
+               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;
 }
 
@@ -403,7 +451,7 @@ static unsigned hexval(int c)
        return ~0;
 }
 
-static int decode_q_segment(char *in, char *ot, char *ep)
+static int decode_q_segment(char *in, char *ot, char *ep, int rfc2047)
 {
        int c;
        while ((c = *in++) != 0 && (in <= ep)) {
@@ -412,9 +460,11 @@ static int decode_q_segment(char *in, char *ot, char *ep)
                        if (d == '\n' || !d)
                                break; /* drop trailing newline */
                        *ot++ = ((hexval(d) << 4) | hexval(*in++));
+                       continue;
                }
-               else
-                       *ot++ = c;
+               if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+                       c = 0x20;
+               *ot++ = c;
        }
        *ot = 0;
        return 0;
@@ -469,6 +519,7 @@ static int decode_b_segment(char *in, char *ot, char *ep)
 
 static void convert_to_utf8(char *line, char *charset)
 {
+#ifndef NO_ICONV
        char *in, *out;
        size_t insize, outsize, nrc;
        char outbuf[4096]; /* cheat */
@@ -501,6 +552,7 @@ static void convert_to_utf8(char *line, char *charset)
                return;
        *out = 0;
        strcpy(line, outbuf);
+#endif
 }
 
 static void decode_header_bq(char *it)
@@ -543,7 +595,7 @@ static void decode_header_bq(char *it)
                        sz = decode_b_segment(cp + 3, piecebuf, ep);
                        break;
                case 'q':
-                       sz = decode_q_segment(cp + 3, piecebuf, ep);
+                       sz = decode_q_segment(cp + 3, piecebuf, ep, 1);
                        break;
                }
                if (sz < 0)
@@ -565,7 +617,7 @@ static void decode_transfer_encoding(char *line)
        switch (transfer_encoding) {
        case TE_QP:
                ep = line + strlen(line);
-               decode_q_segment(line, line, ep);
+               decode_q_segment(line, line, ep, 0);
                break;
        case TE_BASE64:
                ep = line + strlen(line);
@@ -579,25 +631,13 @@ static void decode_transfer_encoding(char *line)
 static void handle_info(void)
 {
        char *sub;
-       static int done_info = 0;
 
-       if (done_info)
-               return;
-
-       done_info = 1;
        sub = cleanup_subject(subject);
        cleanup_space(name);
        cleanup_space(date);
        cleanup_space(email);
        cleanup_space(sub);
 
-       /* Unwrap inline B and Q encoding, and optionally
-        * normalize the meta information to utf8.
-        */
-       decode_header_bq(name);
-       decode_header_bq(date);
-       decode_header_bq(email);
-       decode_header_bq(sub);
        printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n",
               name, email, sub, date);
 }
@@ -605,7 +645,7 @@ static void handle_info(void)
 /* We are inside message body and have read line[] already.
  * Spit out the commit log.
  */
-static int handle_commit_msg(void)
+static int handle_commit_msg(int *seen)
 {
        if (!cmitmsg)
                return 0;
@@ -629,6 +669,11 @@ static int handle_commit_msg(void)
                decode_transfer_encoding(line);
                if (metainfo_charset)
                        convert_to_utf8(line, charset);
+
+               handle_inbody_header(seen, line);
+               if (!(*seen & SEEN_PREFIX))
+                       continue;
+
                fputs(line, cmitmsg);
        } while (fgets(line, sizeof(line), stdin) != NULL);
        fclose(cmitmsg);
@@ -660,26 +705,16 @@ static void handle_patch(void)
  * that the first part to contain commit message and a patch, and
  * handle other parts as pure patches.
  */
-static int handle_multipart_one_part(void)
+static int handle_multipart_one_part(int *seen)
 {
-       int seen = 0;
        int n = 0;
-       int len;
 
        while (fgets(line, sizeof(line), stdin) != NULL) {
        again:
-               len = eatspace(line);
                n++;
-               if (!len)
-                       continue;
                if (is_multipart_boundary(line))
                        break;
-               if (0 <= seen && handle_inbody_header(&seen, line))
-                       continue;
-               seen = -1; /* no more inbody headers */
-               line[len] = '\n';
-               handle_info();
-               if (handle_commit_msg())
+               if (handle_commit_msg(seen))
                        goto again;
                handle_patch();
                break;
@@ -691,6 +726,7 @@ static int handle_multipart_one_part(void)
 
 static void handle_multipart_body(void)
 {
+       int seen = 0;
        int part_num = 0;
 
        /* Skip up to the first boundary */
@@ -703,16 +739,16 @@ static void handle_multipart_body(void)
                return;
        /* We are on boundary line.  Start slurping the subhead. */
        while (1) {
-               int len = read_one_header_line(line, sizeof(line), stdin);
-               if (!len) {
-                       if (handle_multipart_one_part() < 0)
+               int hdr = read_one_header_line(line, sizeof(line), stdin);
+               if (!hdr) {
+                       if (handle_multipart_one_part(&seen) < 0)
                                return;
                        /* Reset per part headers */
                        transfer_encoding = TE_DONTCARE;
                        charset[0] = 0;
                }
                else
-                       check_subheader_line(line, len);
+                       check_subheader_line(line);
        }
        fclose(patchfile);
        if (!patch_lines) {
@@ -726,18 +762,9 @@ static void handle_body(void)
 {
        int seen = 0;
 
-       while (fgets(line, sizeof(line), stdin) != NULL) {
-               int len = eatspace(line);
-               if (!len)
-                       continue;
-               if (0 <= seen && handle_inbody_header(&seen, line))
-                       continue;
-               seen = -1; /* no more inbody headers */
-               line[len] = '\n';
-               handle_info();
-               handle_commit_msg();
+       if (line[0] || fgets(line, sizeof(line), stdin) != NULL) {
+               handle_commit_msg(&seen);
                handle_patch();
-               break;
        }
        fclose(patchfile);
        if (!patch_lines) {
@@ -781,15 +808,16 @@ int main(int argc, char **argv)
                exit(1);
        }
        while (1) {
-               int len = read_one_header_line(line, sizeof(line), stdin);
-               if (!len) {
+               int hdr = read_one_header_line(line, sizeof(line), stdin);
+               if (!hdr) {
                        if (multipart_boundary[0])
                                handle_multipart_body();
                        else
                                handle_body();
+                       handle_info();
                        break;
                }
-               check_header_line(line, len);
+               check_header_line(line);
        }
        return 0;
 }