Skip to content

Commit cce8d6f

Browse files
dschogitster
authored andcommitted
mailsplit and mailinfo: gracefully handle NUL characters
The function fgets() has a big problem with NUL characters: it reads them, but nobody will know if the NUL comes from the file stream, or was appended at the end of the line. So implement a custom read_line_with_nul() function. Noticed by Tommy Thorn. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
1 parent 182fb4d commit cce8d6f

File tree

5 files changed

+46
-15
lines changed

5 files changed

+46
-15
lines changed

builtin-mailinfo.c

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ static void decode_transfer_encoding(char *line, unsigned linesize)
641641
}
642642
}
643643

644-
static int handle_filter(char *line, unsigned linesize);
644+
static int handle_filter(char *line, unsigned linesize, int linelen);
645645

646646
static int find_boundary(void)
647647
{
@@ -669,7 +669,7 @@ static int handle_boundary(void)
669669
"can't recover\n");
670670
exit(1);
671671
}
672-
handle_filter(newline, sizeof(newline));
672+
handle_filter(newline, sizeof(newline), strlen(newline));
673673

674674
/* skip to the next boundary */
675675
if (!find_boundary())
@@ -759,14 +759,14 @@ static int handle_commit_msg(char *line, unsigned linesize)
759759
return 0;
760760
}
761761

762-
static int handle_patch(char *line)
762+
static int handle_patch(char *line, int len)
763763
{
764-
fputs(line, patchfile);
764+
fwrite(line, 1, len, patchfile);
765765
patch_lines++;
766766
return 0;
767767
}
768768

769-
static int handle_filter(char *line, unsigned linesize)
769+
static int handle_filter(char *line, unsigned linesize, int linelen)
770770
{
771771
static int filter = 0;
772772

@@ -779,7 +779,7 @@ static int handle_filter(char *line, unsigned linesize)
779779
break;
780780
filter++;
781781
case 1:
782-
if (!handle_patch(line))
782+
if (!handle_patch(line, linelen))
783783
break;
784784
filter++;
785785
default:
@@ -794,6 +794,7 @@ static void handle_body(void)
794794
int rc = 0;
795795
static char newline[2000];
796796
static char *np = newline;
797+
int len = strlen(line);
797798

798799
/* Skip up to the first boundary */
799800
if (content_top->boundary) {
@@ -807,7 +808,8 @@ static void handle_body(void)
807808
/* flush any leftover */
808809
if ((transfer_encoding == TE_BASE64) &&
809810
(np != newline)) {
810-
handle_filter(newline, sizeof(newline));
811+
handle_filter(newline, sizeof(newline),
812+
strlen(newline));
811813
}
812814
if (!handle_boundary())
813815
return;
@@ -824,7 +826,7 @@ static void handle_body(void)
824826

825827
/* binary data most likely doesn't have newlines */
826828
if (message_type != TYPE_TEXT) {
827-
rc = handle_filter(line, sizeof(newline));
829+
rc = handle_filter(line, sizeof(line), len);
828830
break;
829831
}
830832

@@ -841,7 +843,7 @@ static void handle_body(void)
841843
/* should be sitting on a new line */
842844
*(++np) = 0;
843845
op++;
844-
rc = handle_filter(newline, sizeof(newline));
846+
rc = handle_filter(newline, sizeof(newline), np - newline);
845847
np = newline;
846848
}
847849
} while (*op != 0);
@@ -851,12 +853,12 @@ static void handle_body(void)
851853
break;
852854
}
853855
default:
854-
rc = handle_filter(line, sizeof(newline));
856+
rc = handle_filter(line, sizeof(line), len);
855857
}
856858
if (rc)
857859
/* nothing left to filter */
858860
break;
859-
} while (fgets(line, sizeof(line), fin));
861+
} while ((len = read_line_with_nul(line, sizeof(line), fin)));
860862

861863
return;
862864
}

builtin-mailsplit.c

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ static int is_from_line(const char *line, int len)
4545
/* Could be as small as 64, enough to hold a Unix "From " line. */
4646
static char buf[4096];
4747

48+
/* We cannot use fgets() because our lines can contain NULs */
49+
int read_line_with_nul(char *buf, int size, FILE *in)
50+
{
51+
int len = 0, c;
52+
53+
for (;;) {
54+
c = getc(in);
55+
buf[len++] = c;
56+
if (c == EOF || c == '\n' || len + 1 >= size)
57+
break;
58+
}
59+
60+
if (c == EOF)
61+
len--;
62+
buf[len] = '\0';
63+
64+
return len;
65+
}
66+
4867
/* Called with the first line (potentially partial)
4968
* already in buf[] -- normally that should begin with
5069
* the Unix "From " line. Write it into the specified
@@ -70,19 +89,19 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
7089
* "From " and having something that looks like a date format.
7190
*/
7291
for (;;) {
73-
int is_partial = (buf[len-1] != '\n');
92+
int is_partial = len && buf[len-1] != '\n';
7493

75-
if (fputs(buf, output) == EOF)
94+
if (fwrite(buf, 1, len, output) != len)
7695
die("cannot write output");
7796

78-
if (fgets(buf, sizeof(buf), mbox) == NULL) {
97+
len = read_line_with_nul(buf, sizeof(buf), mbox);
98+
if (len == 0) {
7999
if (feof(mbox)) {
80100
status = 1;
81101
break;
82102
}
83103
die("cannot read mbox");
84104
}
85-
len = strlen(buf);
86105
if (!is_partial && !is_bare && is_from_line(buf, len))
87106
break; /* done with one message */
88107
}

builtin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extern const char git_usage_string[];
99
extern void list_common_cmds_help(void);
1010
extern void help_unknown_cmd(const char *cmd);
1111
extern void prune_packed_objects(int);
12+
extern int read_line_with_nul(char *buf, int size, FILE *file);
1213

1314
extern int cmd_add(int argc, const char **argv, const char *prefix);
1415
extern int cmd_annotate(int argc, const char **argv, const char *prefix);

t/t5100-mailinfo.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,13 @@ do
2525
diff ../t5100/info$mail info$mail"
2626
done
2727

28+
test_expect_success 'respect NULs' '
29+
30+
git mailsplit -d3 -o. ../t5100/nul &&
31+
cmp ../t5100/nul 001 &&
32+
(cat 001 | git mailinfo msg patch) &&
33+
test 4 = $(wc -l < patch)
34+
35+
'
36+
2837
test_done

t/t5100/nul

91 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)