1/*-
2 * Copyright (c) 2008 Joerg Sonnenberger
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "bsdtar_platform.h"
27
28#if defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) || defined(HAVE_PCRE2POSIX_H)
29#include "bsdtar.h"
30
31#include <errno.h>
32#if defined(HAVE_PCREPOSIX_H)
33#include <pcreposix.h>
34#elif defined(HAVE_PCRE2POSIX_H)
35#include <pcre2posix.h>
36#else
37#include <regex.h>
38#endif
39#include <stdlib.h>
40#include <string.h>
41
42#ifndef REG_BASIC
43#define	REG_BASIC 0
44#endif
45
46#include "err.h"
47
48struct subst_rule {
49	struct subst_rule *next;
50	regex_t re;
51	char *result;
52	unsigned int global:1, print:1, regular:1, symlink:1, hardlink:1, from_begin:1;
53};
54
55struct substitution {
56	struct subst_rule *first_rule, *last_rule;
57};
58
59static void
60init_substitution(struct bsdtar *bsdtar)
61{
62	struct substitution *subst;
63
64	bsdtar->substitution = subst = malloc(sizeof(*subst));
65	if (subst == NULL)
66		lafe_errc(1, errno, "Out of memory");
67	subst->first_rule = subst->last_rule = NULL;
68}
69
70void
71add_substitution(struct bsdtar *bsdtar, const char *rule_text)
72{
73	struct subst_rule *rule;
74	struct substitution *subst;
75	const char *end_pattern, *start_subst;
76	char *pattern;
77	int r;
78
79	if ((subst = bsdtar->substitution) == NULL) {
80		init_substitution(bsdtar);
81		subst = bsdtar->substitution;
82	}
83
84	rule = malloc(sizeof(*rule));
85	if (rule == NULL)
86		lafe_errc(1, errno, "Out of memory");
87	rule->next = NULL;
88	rule->result = NULL;
89
90	if (subst->last_rule == NULL)
91		subst->first_rule = rule;
92	else
93		subst->last_rule->next = rule;
94	subst->last_rule = rule;
95
96	if (*rule_text == '\0')
97		lafe_errc(1, 0, "Empty replacement string");
98	end_pattern = strchr(rule_text + 1, *rule_text);
99	if (end_pattern == NULL)
100		lafe_errc(1, 0, "Invalid replacement string");
101
102	pattern = malloc(end_pattern - rule_text);
103	if (pattern == NULL)
104		lafe_errc(1, errno, "Out of memory");
105	memcpy(pattern, rule_text + 1, end_pattern - rule_text - 1);
106	pattern[end_pattern - rule_text - 1] = '\0';
107
108	if ((r = regcomp(&rule->re, pattern, REG_BASIC)) != 0) {
109		char buf[80];
110		regerror(r, &rule->re, buf, sizeof(buf));
111		lafe_errc(1, 0, "Invalid regular expression: %s", buf);
112	}
113	free(pattern);
114
115	start_subst = end_pattern + 1;
116	end_pattern = strchr(start_subst, *rule_text);
117	if (end_pattern == NULL)
118		lafe_errc(1, 0, "Invalid replacement string");
119
120	rule->result = malloc(end_pattern - start_subst + 1);
121	if (rule->result == NULL)
122		lafe_errc(1, errno, "Out of memory");
123	memcpy(rule->result, start_subst, end_pattern - start_subst);
124	rule->result[end_pattern - start_subst] = '\0';
125
126	/* Defaults */
127	rule->global = 0; /* Don't do multiple replacements. */
128	rule->print = 0; /* Don't print. */
129	rule->regular = 1; /* Rewrite regular filenames. */
130	rule->symlink = 1; /* Rewrite symlink targets. */
131	rule->hardlink = 1; /* Rewrite hardlink targets. */
132	rule->from_begin = 0; /* Don't match from start. */
133
134	while (*++end_pattern) {
135		switch (*end_pattern) {
136		case 'b':
137		case 'B':
138			rule->from_begin = 1;
139			break;
140		case 'g':
141		case 'G':
142			rule->global = 1;
143			break;
144		case 'h':
145			rule->hardlink = 1;
146			break;
147		case 'H':
148			rule->hardlink = 0;
149			break;
150		case 'p':
151		case 'P':
152			rule->print = 1;
153			break;
154		case 'r':
155			rule->regular = 1;
156			break;
157		case 'R':
158			rule->regular = 0;
159			break;
160		case 's':
161			rule->symlink = 1;
162			break;
163		case 'S':
164			rule->symlink = 0;
165			break;
166		default:
167			lafe_errc(1, 0, "Invalid replacement flag %c", *end_pattern);
168			/* NOTREACHED */
169		}
170	}
171}
172
173static void
174realloc_strncat(char **str, const char *append, size_t len)
175{
176	char *new_str;
177	size_t old_len;
178
179	if (*str == NULL)
180		old_len = 0;
181	else
182		old_len = strlen(*str);
183
184	new_str = malloc(old_len + len + 1);
185	if (new_str == NULL)
186		lafe_errc(1, errno, "Out of memory");
187	if (*str != NULL)
188		memcpy(new_str, *str, old_len);
189	memcpy(new_str + old_len, append, len);
190	new_str[old_len + len] = '\0';
191	free(*str);
192	*str = new_str;
193}
194
195static void
196realloc_strcat(char **str, const char *append)
197{
198	char *new_str;
199	size_t old_len;
200
201	if (*str == NULL)
202		old_len = 0;
203	else
204		old_len = strlen(*str);
205
206	new_str = malloc(old_len + strlen(append) + 1);
207	if (new_str == NULL)
208		lafe_errc(1, errno, "Out of memory");
209	if (*str != NULL)
210		memcpy(new_str, *str, old_len);
211	strcpy(new_str + old_len, append);
212	free(*str);
213	*str = new_str;
214}
215
216int
217apply_substitution(struct bsdtar *bsdtar, const char *name, char **result,
218    int symlink_target, int hardlink_target)
219{
220	const char *path = name;
221	regmatch_t matches[10];
222	char* buffer = NULL;
223	size_t i, j;
224	struct subst_rule *rule;
225	struct substitution *subst;
226	int c, got_match, print_match;
227
228	*result = NULL;
229
230	if ((subst = bsdtar->substitution) == NULL)
231		return 0;
232
233	got_match = 0;
234	print_match = 0;
235
236	for (rule = subst->first_rule; rule != NULL; rule = rule->next) {
237		if (symlink_target) {
238			if (!rule->symlink)
239				continue;
240		} else if (hardlink_target) {
241			if (!rule->hardlink)
242				continue;
243		} else { /* Regular filename. */
244			if (!rule->regular)
245				continue;
246		}
247
248		if (rule->from_begin && *result) {
249			realloc_strcat(result, name);
250			realloc_strcat(&buffer, *result);
251			name = buffer;
252			(*result)[0] = 0;
253		}
254
255		while (1) {
256			if (regexec(&rule->re, name, 10, matches, 0))
257				break;
258
259			got_match = 1;
260			print_match |= rule->print;
261			realloc_strncat(result, name, matches[0].rm_so);
262
263			for (i = 0, j = 0; rule->result[i] != '\0'; ++i) {
264				if (rule->result[i] == '~') {
265					realloc_strncat(result, rule->result + j, i - j);
266					realloc_strncat(result,
267					    name + matches[0].rm_so,
268					    matches[0].rm_eo - matches[0].rm_so);
269					j = i + 1;
270					continue;
271				}
272				if (rule->result[i] != '\\')
273					continue;
274
275				++i;
276				c = rule->result[i];
277				switch (c) {
278				case '~':
279				case '\\':
280					realloc_strncat(result, rule->result + j, i - j - 1);
281					j = i;
282					break;
283				case '1':
284				case '2':
285				case '3':
286				case '4':
287				case '5':
288				case '6':
289				case '7':
290				case '8':
291				case '9':
292					realloc_strncat(result, rule->result + j, i - j - 1);
293					if ((size_t)(c - '0') > (size_t)(rule->re.re_nsub)) {
294						free(buffer);
295						free(*result);
296						*result = NULL;
297						return -1;
298					}
299					realloc_strncat(result, name + matches[c - '0'].rm_so, matches[c - '0'].rm_eo - matches[c - '0'].rm_so);
300					j = i + 1;
301					break;
302				default:
303					/* Just continue; */
304					break;
305				}
306
307			}
308
309			realloc_strcat(result, rule->result + j);
310
311			name += matches[0].rm_eo;
312
313			if (!rule->global)
314				break;
315		}
316	}
317
318	if (got_match)
319		realloc_strcat(result, name);
320
321	free(buffer);
322
323	if (print_match)
324		fprintf(stderr, "%s >> %s\n", path, *result);
325
326	return got_match;
327}
328
329void
330cleanup_substitution(struct bsdtar *bsdtar)
331{
332	struct subst_rule *rule;
333	struct substitution *subst;
334
335	if ((subst = bsdtar->substitution) == NULL)
336		return;
337
338	while ((rule = subst->first_rule) != NULL) {
339		subst->first_rule = rule->next;
340		free(rule->result);
341		regfree(&rule->re);
342		free(rule);
343	}
344	free(subst);
345}
346#endif /* defined(HAVE_REGEX_H) || defined(HAVE_PCREPOSIX_H) || defined(HAVE_PCRE2POSIX_H) */
347