xref: /openbsd/usr.bin/tmux/regsub.c (revision 6af87e9a)
1 /* $OpenBSD: regsub.c,v 1.6 2023/06/30 21:55:09 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <regex.h>
22 #include <string.h>
23 
24 #include "tmux.h"
25 
26 static void
regsub_copy(char ** buf,ssize_t * len,const char * text,size_t start,size_t end)27 regsub_copy(char **buf, ssize_t *len, const char *text, size_t start, size_t end)
28 {
29 	size_t	add = end - start;
30 
31 	*buf = xrealloc(*buf, (*len) + add + 1);
32 	memcpy((*buf) + *len, text + start, add);
33 	(*len) += add;
34 }
35 
36 static void
regsub_expand(char ** buf,ssize_t * len,const char * with,const char * text,regmatch_t * m,u_int n)37 regsub_expand(char **buf, ssize_t *len, const char *with, const char *text,
38     regmatch_t *m, u_int n)
39 {
40 	const char	*cp;
41 	u_int		 i;
42 
43 	for (cp = with; *cp != '\0'; cp++) {
44 		if (*cp == '\\') {
45 			cp++;
46 			if (*cp >= '0' && *cp <= '9') {
47 				i = *cp - '0';
48 				if (i < n && m[i].rm_so != m[i].rm_eo) {
49 					regsub_copy(buf, len, text, m[i].rm_so,
50 					    m[i].rm_eo);
51 					continue;
52 				}
53 			}
54 		}
55 		*buf = xrealloc(*buf, (*len) + 2);
56 		(*buf)[(*len)++] = *cp;
57 	}
58 }
59 
60 char *
regsub(const char * pattern,const char * with,const char * text,int flags)61 regsub(const char *pattern, const char *with, const char *text, int flags)
62 {
63 	regex_t		 r;
64 	regmatch_t	 m[10];
65 	ssize_t		 start, end, last, len = 0;
66 	int		 empty = 0;
67 	char		*buf = NULL;
68 
69 	if (*text == '\0')
70 		return (xstrdup(""));
71 	if (regcomp(&r, pattern, flags) != 0)
72 		return (NULL);
73 
74 	start = 0;
75 	last = 0;
76 	end = strlen(text);
77 
78 	while (start <= end) {
79 		if (regexec(&r, text + start, nitems(m), m, 0) != 0) {
80 			regsub_copy(&buf, &len, text, start, end);
81 			break;
82 		}
83 
84 		/*
85 		 * Append any text not part of this match (from the end of the
86 		 * last match).
87 		 */
88 		regsub_copy(&buf, &len, text, last, m[0].rm_so + start);
89 
90 		/*
91 		 * If the last match was empty and this one isn't (it is either
92 		 * later or has matched text), expand this match. If it is
93 		 * empty, move on one character and try again from there.
94 		 */
95 		if (empty ||
96 		    start + m[0].rm_so != last ||
97 		    m[0].rm_so != m[0].rm_eo) {
98 			regsub_expand(&buf, &len, with, text + start, m,
99 			    nitems(m));
100 
101 			last = start + m[0].rm_eo;
102 			start += m[0].rm_eo;
103 			empty = 0;
104 		} else {
105 			last = start + m[0].rm_eo;
106 			start += m[0].rm_eo + 1;
107 			empty = 1;
108 		}
109 
110 		/* Stop now if anchored to start. */
111 		if (*pattern == '^') {
112 			regsub_copy(&buf, &len, text, start, end);
113 			break;
114 		}
115 	}
116 	buf[len] = '\0';
117 
118 	regfree(&r);
119 	return (buf);
120 }
121