Raw
1 #include "git-compat-util.h"
2
3 #include <wchar.h>
4
5 /*
6 * Darwin's TRE regex engine leaks an internal buffer when it encounters an
7 * invalid multibyte sequence. Since the leak has already happened when
8 * regexec() reports REG_ILLSEQ, keep invalid bytes out of regexec() by
9 * searching each valid segment separately.
10 */
11
12 /*
13 * Search buf[start, end), where size is the full size of buf. REG_STARTEND
14 * keeps match offsets relative to buf. Do not let an internal segment create
15 * a false beginning or end of line.
16 */
17 static int regexec_segment(const regex_t *preg, const char *buf,
18 size_t size, size_t start, size_t end,
19 size_t nmatch, regmatch_t pmatch[], int eflags)
20 {
21 eflags |= REG_STARTEND;
22 if (start > 0)
23 eflags |= REG_NOTBOL;
24 if (end < size)
25 eflags |= REG_NOTEOL;
26 pmatch[0].rm_so = start;
27 pmatch[0].rm_eo = end;
28 return regexec(preg, buf, nmatch, pmatch, eflags);
29 }
30
31 int darwin_regexec_buf(const regex_t *preg, const char *buf, size_t size,
32 size_t nmatch, regmatch_t pmatch[], int eflags)
33 {
34 size_t seg_start = 0, i = 0;
35 mbstate_t mbs;
36
37 assert(nmatch > 0 && pmatch);
38
39 /*
40 * A single-byte locale cannot contain an invalid multibyte sequence,
41 * so use regexec() directly.
42 */
43 if (MB_CUR_MAX == 1) {
44 pmatch[0].rm_so = 0;
45 pmatch[0].rm_eo = size;
46 return regexec(preg, buf, nmatch, pmatch, eflags | REG_STARTEND);
47 }
48
49 memset(&mbs, 0, sizeof(mbs));
50 while (i < size) {
51 unsigned char c = (unsigned char)buf[i];
52 size_t n;
53
54 if (c < 0x80) {
55 i++;
56 continue;
57 }
58
59 n = mbrtowc(NULL, buf + i, size - i, &mbs);
60 if (!n)
61 n = 1;
62 if (n != (size_t)-1 && n != (size_t)-2) {
63 i += n;
64 continue;
65 }
66
67 /*
68 * -1 denotes an encoding error; -2 denotes an incomplete
69 * trailing sequence. In either case, buf[i] cannot begin a
70 * complete valid character within this buffer. Search an
71 * empty initial segment to preserve zero-width matches at the
72 * true beginning.
73 */
74 if (i > seg_start || i == 0) {
75 int ret = regexec_segment(preg, buf, size, seg_start, i,
76 nmatch, pmatch, eflags);
77 if (ret != REG_NOMATCH)
78 return ret;
79 }
80 i++;
81 seg_start = i;
82 memset(&mbs, 0, sizeof(mbs));
83 }
84
85 /*
86 * Search the final segment even when it is empty, so an empty buffer
87 * or a buffer ending in invalid bytes still has its true end.
88 */
89 return regexec_segment(preg, buf, size, seg_start, size,
90 nmatch, pmatch, eflags);
91 }