push: propagate remote and refspec with --recurse-submodules
[gitweb.git] / mailinfo.c
index 97e5cb8d70c81017ce3c73fb3a70cedbe07053d7..a489d9d0fbcc37a1198247a8daae4b3104ebe284 100644 (file)
@@ -54,6 +54,86 @@ static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
        get_sane_name(&mi->name, &mi->name, &mi->email);
 }
 
+static const char *unquote_comment(struct strbuf *outbuf, const char *in)
+{
+       int c;
+       int take_next_litterally = 0;
+
+       strbuf_addch(outbuf, '(');
+
+       while ((c = *in++) != 0) {
+               if (take_next_litterally == 1) {
+                       take_next_litterally = 0;
+               } else {
+                       switch (c) {
+                       case '\\':
+                               take_next_litterally = 1;
+                               continue;
+                       case '(':
+                               in = unquote_comment(outbuf, in);
+                               continue;
+                       case ')':
+                               strbuf_addch(outbuf, ')');
+                               return in;
+                       }
+               }
+
+               strbuf_addch(outbuf, c);
+       }
+
+       return in;
+}
+
+static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
+{
+       int c;
+       int take_next_litterally = 0;
+
+       while ((c = *in++) != 0) {
+               if (take_next_litterally == 1) {
+                       take_next_litterally = 0;
+               } else {
+                       switch (c) {
+                       case '\\':
+                               take_next_litterally = 1;
+                               continue;
+                       case '"':
+                               return in;
+                       }
+               }
+
+               strbuf_addch(outbuf, c);
+       }
+
+       return in;
+}
+
+static void unquote_quoted_pair(struct strbuf *line)
+{
+       struct strbuf outbuf;
+       const char *in = line->buf;
+       int c;
+
+       strbuf_init(&outbuf, line->len);
+
+       while ((c = *in++) != 0) {
+               switch (c) {
+               case '"':
+                       in = unquote_quoted_string(&outbuf, in);
+                       continue;
+               case '(':
+                       in = unquote_comment(&outbuf, in);
+                       continue;
+               }
+
+               strbuf_addch(&outbuf, c);
+       }
+
+       strbuf_swap(&outbuf, line);
+       strbuf_release(&outbuf);
+
+}
+
 static void handle_from(struct mailinfo *mi, const struct strbuf *from)
 {
        char *at;
@@ -63,6 +143,8 @@ static void handle_from(struct mailinfo *mi, const struct strbuf *from)
        strbuf_init(&f, from->len);
        strbuf_addbuf(&f, from);
 
+       unquote_quoted_pair(&f);
+
        at = strchr(f.buf, '@');
        if (!at) {
                parse_bogus_from(mi, from);
@@ -163,8 +245,10 @@ static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
        if (slurp_attr(line->buf, "boundary=", boundary)) {
                strbuf_insert(boundary, 0, "--", 2);
                if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
-                       fprintf(stderr, "Too many boundaries to handle\n");
-                       exit(1);
+                       error("Too many boundaries to handle");
+                       mi->input_error = -1;
+                       mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
+                       return;
                }
                *(mi->content_top) = boundary;
                boundary = NULL;
@@ -177,12 +261,6 @@ static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
        }
 }
 
-static void handle_message_id(struct mailinfo *mi, const struct strbuf *line)
-{
-       if (mi->add_message_id)
-               mi->message_id = strdup(line->buf);
-}
-
 static void handle_content_transfer_encoding(struct mailinfo *mi,
                                             const struct strbuf *line)
 {
@@ -355,9 +433,11 @@ static int convert_to_utf8(struct mailinfo *mi,
        if (same_encoding(mi->metainfo_charset, charset))
                return 0;
        out = reencode_string(line->buf, mi->metainfo_charset, charset);
-       if (!out)
+       if (!out) {
+               mi->input_error = -1;
                return error("cannot convert from %s to %s",
                             charset, mi->metainfo_charset);
+       }
        strbuf_attach(line, out, strlen(out), strlen(out));
        return 0;
 }
@@ -367,6 +447,7 @@ static void decode_header(struct mailinfo *mi, struct strbuf *it)
        char *in, *ep, *cp;
        struct strbuf outbuf = STRBUF_INIT, *dec;
        struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+       int found_error = 1; /* pessimism */
 
        in = it->buf;
        while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
@@ -436,10 +517,14 @@ static void decode_header(struct mailinfo *mi, struct strbuf *it)
        strbuf_addstr(&outbuf, in);
        strbuf_reset(it);
        strbuf_addbuf(it, &outbuf);
+       found_error = 0;
 release_return:
        strbuf_release(&outbuf);
        strbuf_release(&charset_q);
        strbuf_release(&piecebuf);
+
+       if (found_error)
+               mi->input_error = -1;
 }
 
 static int check_header(struct mailinfo *mi,
@@ -486,31 +571,32 @@ static int check_header(struct mailinfo *mi,
                len = strlen("Message-Id: ");
                strbuf_add(&sb, line->buf + len, line->len - len);
                decode_header(mi, &sb);
-               handle_message_id(mi, &sb);
+               if (mi->add_message_id)
+                       mi->message_id = strbuf_detach(&sb, NULL);
                ret = 1;
                goto check_header_out;
        }
 
-       /* for inbody stuff */
-       if (starts_with(line->buf, ">From") && isspace(line->buf[5])) {
-               ret = is_format_patch_separator(line->buf + 1, line->len - 1);
-               goto check_header_out;
-       }
-       if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
-               for (i = 0; header[i]; i++) {
-                       if (!strcmp("Subject", header[i])) {
-                               handle_header(&hdr_data[i], line);
-                               ret = 1;
-                               goto check_header_out;
-                       }
-               }
-       }
-
 check_header_out:
        strbuf_release(&sb);
        return ret;
 }
 
+/*
+ * Returns 1 if the given line or any line beginning with the given line is an
+ * in-body header (that is, check_header will succeed when passed
+ * mi->s_hdr_data).
+ */
+static int is_inbody_header(const struct mailinfo *mi,
+                           const struct strbuf *line)
+{
+       int i;
+       for (i = 0; header[i]; i++)
+               if (!mi->s_hdr_data[i] && cmp_header(line, header[i]))
+                       return 1;
+       return 0;
+}
+
 static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
 {
        struct strbuf *ret;
@@ -568,37 +654,35 @@ static inline int patchbreak(const struct strbuf *line)
        return 0;
 }
 
-static int is_scissors_line(const struct strbuf *line)
+static int is_scissors_line(const char *line)
 {
-       size_t i, len = line->len;
+       const char *c;
        int scissors = 0, gap = 0;
-       int first_nonblank = -1;
-       int last_nonblank = 0, visible, perforation = 0, in_perforation = 0;
-       const char *buf = line->buf;
+       const char *first_nonblank = NULL, *last_nonblank = NULL;
+       int visible, perforation = 0, in_perforation = 0;
 
-       for (i = 0; i < len; i++) {
-               if (isspace(buf[i])) {
+       for (c = line; *c; c++) {
+               if (isspace(*c)) {
                        if (in_perforation) {
                                perforation++;
                                gap++;
                        }
                        continue;
                }
-               last_nonblank = i;
-               if (first_nonblank < 0)
-                       first_nonblank = i;
-               if (buf[i] == '-') {
+               last_nonblank = c;
+               if (first_nonblank == NULL)
+                       first_nonblank = c;
+               if (*c == '-') {
                        in_perforation = 1;
                        perforation++;
                        continue;
                }
-               if (i + 1 < len &&
-                   (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2) ||
-                    !memcmp(buf + i, ">%", 2) || !memcmp(buf + i, "%<", 2))) {
+               if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) ||
+                    !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) {
                        in_perforation = 1;
                        perforation += 2;
                        scissors += 2;
-                       i++;
+                       c++;
                        continue;
                }
                in_perforation = 0;
@@ -613,12 +697,61 @@ static int is_scissors_line(const struct strbuf *line)
         * than half of the perforation.
         */
 
-       visible = last_nonblank - first_nonblank + 1;
+       if (first_nonblank && last_nonblank)
+               visible = last_nonblank - first_nonblank + 1;
+       else
+               visible = 0;
        return (scissors && 8 <= visible &&
                visible < perforation * 3 &&
                gap * 2 < perforation);
 }
 
+static void flush_inbody_header_accum(struct mailinfo *mi)
+{
+       if (!mi->inbody_header_accum.len)
+               return;
+       if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
+               die("BUG: inbody_header_accum, if not empty, must always contain a valid in-body header");
+       strbuf_reset(&mi->inbody_header_accum);
+}
+
+static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)
+{
+       if (mi->inbody_header_accum.len &&
+           (line->buf[0] == ' ' || line->buf[0] == '\t')) {
+               if (mi->use_scissors && is_scissors_line(line->buf)) {
+                       /*
+                        * This is a scissors line; do not consider this line
+                        * as a header continuation line.
+                        */
+                       flush_inbody_header_accum(mi);
+                       return 0;
+               }
+               strbuf_strip_suffix(&mi->inbody_header_accum, "\n");
+               strbuf_addbuf(&mi->inbody_header_accum, line);
+               return 1;
+       }
+
+       flush_inbody_header_accum(mi);
+
+       if (starts_with(line->buf, ">From") && isspace(line->buf[5]))
+               return is_format_patch_separator(line->buf + 1, line->len - 1);
+       if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+               int i;
+               for (i = 0; header[i]; i++)
+                       if (!strcmp("Subject", header[i])) {
+                               handle_header(&mi->s_hdr_data[i], line);
+                               return 1;
+                       }
+               return 0;
+       }
+       if (is_inbody_header(mi, line)) {
+               strbuf_addbuf(&mi->inbody_header_accum, line);
+               return 1;
+       }
+       return 0;
+}
+
 static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
 {
        assert(!mi->filter_stage);
@@ -629,7 +762,7 @@ static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
        }
 
        if (mi->use_inbody_headers && mi->header_stage) {
-               mi->header_stage = check_header(mi, line, mi->s_hdr_data, 0);
+               mi->header_stage = check_inbody_header(mi, line);
                if (mi->header_stage)
                        return 0;
        } else
@@ -640,9 +773,9 @@ static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
 
        /* normalize the log message to UTF-8. */
        if (convert_to_utf8(mi, line, mi->charset.buf))
-               exit(128);
+               return 0; /* mi->input_error already set */
 
-       if (mi->use_scissors && is_scissors_line(line)) {
+       if (mi->use_scissors && is_scissors_line(line->buf)) {
                int i;
 
                strbuf_setlen(&mi->log_message, 0);
@@ -723,7 +856,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in)
        struct strbuf continuation = STRBUF_INIT;
 
        /* Get the first part of the line. */
-       if (strbuf_getline(line, in, '\n'))
+       if (strbuf_getline_lf(line, in))
                return 0;
 
        /*
@@ -747,7 +880,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in)
                peek = fgetc(in); ungetc(peek, in);
                if (peek != ' ' && peek != '\t')
                        break;
-               if (strbuf_getline(&continuation, in, '\n'))
+               if (strbuf_getline_lf(&continuation, in))
                        break;
                continuation.buf[0] = ' ';
                strbuf_rtrim(&continuation);
@@ -760,7 +893,7 @@ static int read_one_header_line(struct strbuf *line, FILE *in)
 
 static int find_boundary(struct mailinfo *mi, struct strbuf *line)
 {
-       while (!strbuf_getline(line, mi->input, '\n')) {
+       while (!strbuf_getline_lf(line, mi->input)) {
                if (*(mi->content_top) && is_multipart_boundary(mi, line))
                        return 1;
        }
@@ -785,12 +918,15 @@ static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
                   will fail first.  But just in case..
                 */
                if (--mi->content_top < mi->content) {
-                       fprintf(stderr, "Detected mismatched boundaries, "
-                                       "can't recover\n");
-                       exit(1);
+                       error("Detected mismatched boundaries, can't recover");
+                       mi->input_error = -1;
+                       mi->content_top = mi->content;
+                       return 0;
                }
                handle_filter(mi, &newline);
                strbuf_release(&newline);
+               if (mi->input_error)
+                       return 0;
 
                /* skip to the next boundary */
                if (!find_boundary(mi, line))
@@ -808,7 +944,7 @@ static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
 
        strbuf_release(&newline);
        /* replenish line */
-       if (strbuf_getline(line, mi->input, '\n'))
+       if (strbuf_getline_lf(line, mi->input))
                return 0;
        strbuf_addch(line, '\n');
        return 1;
@@ -875,8 +1011,12 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
                        handle_filter(mi, line);
                }
 
+               if (mi->input_error)
+                       break;
        } while (!strbuf_getwholeline(line, mi->input, '\n'));
 
+       flush_inbody_header_accum(mi);
+
 handle_body_out:
        strbuf_release(&prev);
 }
@@ -968,7 +1108,7 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
 
        handle_info(mi);
        strbuf_release(&line);
-       return 0;
+       return mi->input_error;
 }
 
 static int git_mailinfo_config(const char *var, const char *value, void *mi_)
@@ -992,10 +1132,11 @@ void setup_mailinfo(struct mailinfo *mi)
        strbuf_init(&mi->email, 0);
        strbuf_init(&mi->charset, 0);
        strbuf_init(&mi->log_message, 0);
+       strbuf_init(&mi->inbody_header_accum, 0);
        mi->header_stage = 1;
        mi->use_inbody_headers = 1;
        mi->content_top = mi->content;
-       git_config(git_mailinfo_config, &mi);
+       git_config(git_mailinfo_config, mi);
 }
 
 void clear_mailinfo(struct mailinfo *mi)
@@ -1005,6 +1146,7 @@ void clear_mailinfo(struct mailinfo *mi)
        strbuf_release(&mi->name);
        strbuf_release(&mi->email);
        strbuf_release(&mi->charset);
+       strbuf_release(&mi->inbody_header_accum);
        free(mi->message_id);
 
        for (i = 0; mi->p_hdr_data[i]; i++)