xref: /freebsd/contrib/less/pattern.c (revision 4523eebc6c1828d4fd41d7f1ab943cf079043bc8)
1 /*
2  * Copyright (C) 1984-2026  Mark Nudelman
3  *
4  * You may distribute under the terms of either the GNU General Public
5  * License or the Less License, as specified in the README file.
6  *
7  * For more information, see the README file.
8  */
9 
10 /*
11  * Routines to do pattern matching.
12  */
13 
14 #include "less.h"
15 
16 extern int caseless;
17 extern int is_caseless;
18 extern int utf_mode;
19 
20 /*
21  * Compile a search pattern, for future use by match_pattern.
22  */
compile_pattern2(constant char * pattern,int search_type,PATTERN_TYPE * comp_pattern,int show_error)23 static int compile_pattern2(constant char *pattern, int search_type, PATTERN_TYPE *comp_pattern, int show_error)
24 {
25 	if (search_type & SRCH_NO_REGEX)
26 		return (0);
27   {
28 #if HAVE_GNU_REGEX
29 	struct re_pattern_buffer *comp = (struct re_pattern_buffer *)
30 		ecalloc(1, sizeof(struct re_pattern_buffer));
31 	re_set_syntax(RE_SYNTAX_POSIX_EXTENDED);
32 	if (re_compile_pattern(pattern, strlen(pattern), comp))
33 	{
34 		free(comp);
35 		if (show_error)
36 			error(LM(Invalid_pattern), NULL_PARG);
37 		return (-1);
38 	}
39 	if (*comp_pattern != NULL)
40 	{
41 		regfree(*comp_pattern);
42 		free(*comp_pattern);
43 	}
44 	*comp_pattern = comp;
45 #endif
46 #if HAVE_POSIX_REGCOMP
47 	regex_t *comp = (regex_t *) ecalloc(1, sizeof(regex_t));
48 	if (regcomp(comp, pattern, REGCOMP_FLAG | (is_caseless ? REG_ICASE : 0)))
49 	{
50 		free(comp);
51 		if (show_error)
52 			error(LM(Invalid_pattern), NULL_PARG);
53 		return (-1);
54 	}
55 	if (*comp_pattern != NULL)
56 	{
57 		regfree(*comp_pattern);
58 		free(*comp_pattern);
59 	}
60 	*comp_pattern = comp;
61 #endif
62 #if HAVE_PCRE
63 	constant char *errstring;
64 	int erroffset;
65 	PARG parg;
66 	pcre *comp = pcre_compile(pattern,
67 			((utf_mode) ? PCRE_UTF8 | PCRE_NO_UTF8_CHECK : 0) |
68 			(is_caseless ? PCRE_CASELESS : 0),
69 			&errstring, &erroffset, NULL);
70 	if (comp == NULL)
71 	{
72 		parg.p_string = (char *) errstring;
73 		if (show_error)
74 			error("%s", &parg);
75 		return (-1);
76 	}
77 	*comp_pattern = comp;
78 #endif
79 #if HAVE_PCRE2
80 	int errcode;
81 	PCRE2_SIZE erroffset;
82 	PARG parg;
83 	pcre2_code *comp = pcre2_compile((PCRE2_SPTR)pattern, strlen(pattern),
84 			((utf_mode) ? PCRE2_UTF | PCRE2_NO_UTF_CHECK : 0) |
85 			(is_caseless ? PCRE2_CASELESS : 0),
86 			&errcode, &erroffset, NULL);
87 	if (comp == NULL)
88 	{
89 		if (show_error)
90 		{
91 			char msg[160];
92 			pcre2_get_error_message(errcode, (PCRE2_UCHAR*)msg, sizeof(msg));
93 			parg.p_string = msg;
94 			error("%s", &parg);
95 		}
96 		return (-1);
97 	}
98 	*comp_pattern = comp;
99 #endif
100 #if HAVE_RE_COMP
101 	PARG parg;
102 	if ((parg.p_string = re_comp(pattern)) != NULL)
103 	{
104 		if (show_error)
105 			error("%s", &parg);
106 		return (-1);
107 	}
108 	*comp_pattern = 1;
109 #endif
110 #if HAVE_REGCMP
111 	char *comp;
112 	if ((comp = regcmp(pattern, 0)) == NULL)
113 	{
114 		if (show_error)
115 			error(LM(Invalid_pattern), NULL_PARG);
116 		return (-1);
117 	}
118 	if (comp_pattern != NULL)
119 		free(*comp_pattern);
120 	*comp_pattern = comp;
121 #endif
122 #if HAVE_V8_REGCOMP
123 	struct regexp *comp;
124 	reg_show_error = show_error;
125 	comp = regcomp(pattern);
126 	reg_show_error = 1;
127 	if (comp == NULL)
128 	{
129 		/*
130 		 * regcomp has already printed an error message
131 		 * via regerror().
132 		 */
133 		return (-1);
134 	}
135 	if (*comp_pattern != NULL)
136 		free(*comp_pattern);
137 	*comp_pattern = comp;
138 #endif
139   }
140 	return (0);
141 }
142 
143 /*
144  * Like compile_pattern2, but convert the pattern to lowercase if necessary.
145  */
compile_pattern(constant char * pattern,int search_type,int show_error,PATTERN_TYPE * comp_pattern)146 public int compile_pattern(constant char *pattern, int search_type, int show_error, PATTERN_TYPE *comp_pattern)
147 {
148 	int result;
149 
150 #if RE_HANDLES_CASELESS
151 	if (caseless != OPT_ONPLUS || (!(search_type & SRCH_NO_REGEX)))
152 #else
153 	if (caseless != OPT_ONPLUS)
154 #endif
155 	{
156 		result = compile_pattern2(pattern, search_type, comp_pattern, show_error);
157 	} else
158 	{
159 		char *cvt_pattern = (char*) ecalloc(1, cvt_length(strlen(pattern), CVT_TO_LC));
160 		cvt_text(cvt_pattern, pattern, NULL, NULL, CVT_TO_LC);
161 		result = compile_pattern2(cvt_pattern, search_type, comp_pattern, show_error);
162 		free(cvt_pattern);
163 	}
164 	return (result);
165 }
166 
167 /*
168  * Forget that we have a compiled pattern.
169  */
uncompile_pattern(PATTERN_TYPE * pattern)170 public void uncompile_pattern(PATTERN_TYPE *pattern)
171 {
172 #if HAVE_GNU_REGEX
173 	if (*pattern != NULL)
174 	{
175 		regfree(*pattern);
176 		free(*pattern);
177 	}
178 	*pattern = NULL;
179 #endif
180 #if HAVE_POSIX_REGCOMP
181 	if (*pattern != NULL)
182 	{
183 		regfree(*pattern);
184 		free(*pattern);
185 	}
186 	*pattern = NULL;
187 #endif
188 #if HAVE_PCRE
189 	if (*pattern != NULL)
190 		pcre_free(*pattern);
191 	*pattern = NULL;
192 #endif
193 #if HAVE_PCRE2
194 	if (*pattern != NULL)
195 		pcre2_code_free(*pattern);
196 	*pattern = NULL;
197 #endif
198 #if HAVE_RE_COMP
199 	*pattern = 0;
200 #endif
201 #if HAVE_REGCMP
202 	if (*pattern != NULL)
203 		free(*pattern);
204 	*pattern = NULL;
205 #endif
206 #if HAVE_V8_REGCOMP
207 	if (*pattern != NULL)
208 		free(*pattern);
209 	*pattern = NULL;
210 #endif
211 }
212 
213 #if 0
214 /*
215  * Can a pattern be successfully compiled?
216  */
217 public int valid_pattern(char *pattern)
218 {
219 	PATTERN_TYPE comp_pattern;
220 	int result;
221 
222 	SET_NULL_PATTERN(comp_pattern);
223 	result = compile_pattern2(pattern, 0, &comp_pattern, 0);
224 	if (result != 0)
225 		return (0);
226 	uncompile_pattern(&comp_pattern);
227 	return (1);
228 }
229 #endif
230 
231 /*
232  * Is a compiled pattern null?
233  */
is_null_pattern(PATTERN_TYPE pattern)234 public lbool is_null_pattern(PATTERN_TYPE pattern)
235 {
236 #if HAVE_GNU_REGEX
237 	return (pattern == NULL);
238 #endif
239 #if HAVE_POSIX_REGCOMP
240 	return (pattern == NULL);
241 #endif
242 #if HAVE_PCRE
243 	return (pattern == NULL);
244 #endif
245 #if HAVE_PCRE2
246 	return (pattern == NULL);
247 #endif
248 #if HAVE_RE_COMP
249 	return (pattern == 0);
250 #endif
251 #if HAVE_REGCMP
252 	return (pattern == NULL);
253 #endif
254 #if HAVE_V8_REGCOMP
255 	return (pattern == NULL);
256 #endif
257 #if NO_REGEX
258 	return (pattern == NULL);
259 #endif
260 }
261 /*
262  * Simple pattern matching function.
263  * It supports no metacharacters like *, etc.
264  */
match(constant char * pattern,size_t pattern_len,constant char * buf,int buf_len,constant char *** sp,constant char *** ep,int nsubs)265 static int match(constant char *pattern, size_t pattern_len, constant char *buf, int buf_len, constant char ***sp, constant char ***ep, int nsubs)
266 {
267 	constant char *pp;
268 	constant char *lp;
269 	constant char *pattern_end = pattern + pattern_len;
270 	constant char *buf_end = buf + buf_len;
271 
272 	(void) nsubs;
273 	for ( ;  buf < buf_end;  buf++)
274 	{
275 		for (pp = pattern, lp = buf;  ;  pp++, lp++)
276 		{
277 			char cp = *pp;
278 			char cl = *lp;
279 			if (caseless == OPT_ONPLUS && ASCII_IS_UPPER(cp))
280 				cp = ASCII_TO_LOWER(cp);
281 			if (cp != cl)
282 				break;
283 			if (pp == pattern_end || lp == buf_end)
284 				break;
285 		}
286 		if (pp == pattern_end)
287 		{
288 			*(*sp)++ = buf;
289 			*(*ep)++ = lp;
290 			return (1);
291 		}
292 	}
293 	**sp = **ep = NULL;
294 	return (0);
295 }
296 
297 /*
298  * Perform a pattern match with the previously compiled pattern.
299  * Set sp[0] and ep[0] to the start and end of the matched string.
300  * Set sp[i] and ep[i] to the start and end of the i-th matched subpattern.
301  * Subpatterns are defined by parentheses in the regex language.
302  */
match_pattern1(PATTERN_TYPE pattern,constant char * tpattern,constant char * line,size_t aline_len,size_t line_off,constant char ** sp,constant char ** ep,int nsp,int notbol,int search_type)303 static lbool match_pattern1(PATTERN_TYPE pattern, constant char *tpattern, constant char *line, size_t aline_len, size_t line_off, constant char **sp, constant char **ep, int nsp, int notbol, int search_type)
304 {
305 	int matched;
306 	int line_len = (int) aline_len; /*{{type-issue}}*/
307 
308 #if NO_REGEX
309 	search_type |= SRCH_NO_REGEX;
310 #endif
311 	if (search_type & SRCH_NO_REGEX)
312 		matched = match(tpattern, strlen(tpattern), line + line_off, line_len - line_off, &sp, &ep, nsp);
313 	else
314 	{
315 #if HAVE_GNU_REGEX
316 	{
317 		struct re_registers search_regs;
318 		pattern->not_bol = notbol;
319 		pattern->regs_allocated = REGS_UNALLOCATED;
320 		matched = re_search(pattern, line, line_len, line_off, line_len - line_off, &search_regs) >= 0;
321 		if (matched)
322 		{
323 			*sp++ = line + search_regs.start[0];
324 			*ep++ = line + search_regs.end[0];
325 		}
326 	}
327 #endif
328 #if HAVE_POSIX_REGCOMP
329 	{
330 		#define RM_COUNT (NUM_SEARCH_COLORS+2)
331 		regmatch_t rm[RM_COUNT];
332 		int flags = (notbol) ? REG_NOTBOL : 0;
333 #ifdef REG_STARTEND
334 		flags |= REG_STARTEND;
335 		rm[0].rm_so = line_off;
336 		rm[0].rm_eo = line_len;
337 #else
338 		line += line_off;
339 #endif
340 		matched = !regexec(pattern, line, RM_COUNT, rm, flags);
341 		if (matched)
342 		{
343 			int i;
344 			int ecount;
345 			for (ecount = RM_COUNT;  ecount > 0;  ecount--)
346 				if (RM_VALID(&rm[ecount-1]))
347 					break;
348 			if (ecount >= nsp)
349 				ecount = nsp-1;
350 			for (i = 0;  i < ecount;  i++)
351 			{
352 				if (!RM_VALID(&rm[i]))
353 				{
354 					*sp++ = *ep++ = line;
355 				} else
356 				{
357 					*sp++ = RM_PTR(&rm[i], line);
358 					*ep++ = RM_EPTR(&rm[i], line);
359 				}
360 			}
361 		}
362 	}
363 #endif
364 #if HAVE_PCRE
365 	{
366 		#define OVECTOR_COUNT ((3*NUM_SEARCH_COLORS)+3)
367 		int ovector[OVECTOR_COUNT];
368 		int flags = (notbol) ? PCRE_NOTBOL : 0;
369 		int i;
370 		int ecount;
371 		int mcount = pcre_exec(pattern, NULL, line, line_len,
372 			line_off, flags, ovector, OVECTOR_COUNT);
373 		matched = (mcount > 0);
374 		ecount = nsp-1;
375 		if (ecount > mcount) ecount = mcount;
376 		for (i = 0;  i < ecount*2; )
377 		{
378 			if (ovector[i] < 0 || ovector[i+1] < 0)
379 			{
380 				*sp++ = *ep++ = line;
381 				i += 2;
382 			} else
383 			{
384 				*sp++ = line + ovector[i++];
385 				*ep++ = line + ovector[i++];
386 			}
387 		}
388 	}
389 #endif
390 #if HAVE_PCRE2
391 	{
392 		int flags = (notbol) ? PCRE2_NOTBOL : 0;
393 		pcre2_match_data *md = pcre2_match_data_create_from_pattern(pattern, NULL);
394 		int mcount = pcre2_match(pattern, (PCRE2_SPTR)line, line_len,
395 			line_off, flags, md, NULL);
396 		matched = (mcount > 0);
397 		if (matched)
398 		{
399 			PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(md);
400 			int i;
401 			int ecount = nsp-1;
402 			if (ecount > mcount) ecount = mcount;
403 			for (i = 0;  i < ecount*2; )
404 			{
405 				if (ovector[i] == PCRE2_UNSET || ovector[i+1] == PCRE2_UNSET)
406 				{
407 					*sp++ = *ep++ = line;
408 					i += 2;
409 				} else
410 				{
411 					*sp++ = line + ovector[i++];
412 					*ep++ = line + ovector[i++];
413 				}
414 			}
415 		}
416 		pcre2_match_data_free(md);
417 	}
418 #endif
419 #if HAVE_RE_COMP
420 	matched = (re_exec(line + line_off) == 1);
421 	/*
422 	 * re_exec doesn't seem to provide a way to get the matched string.
423 	 */
424 #endif
425 #if HAVE_REGCMP
426 	matched = ((*ep++ = regex(pattern, line + line_off)) != NULL);
427 	if (matched)
428 		*sp++ = __loc1;
429 #endif
430 #if HAVE_V8_REGCOMP
431 #if HAVE_REGEXEC2
432 	matched = regexec2(pattern, line + line_off, notbol);
433 #else
434 	matched = regexec(pattern, line + line_off);
435 #endif
436 	if (matched)
437 	{
438 		*sp++ = pattern->startp[0];
439 		*ep++ = pattern->endp[0];
440 	}
441 #endif
442 	}
443 	*sp = *ep = NULL;
444 	matched = (!(search_type & SRCH_NO_MATCH) && matched) ||
445 			((search_type & SRCH_NO_MATCH) && !matched);
446 	return (matched != 0);
447 }
448 
449 /*
450  * Return TRUE if the match satisfies all conditions in *subsearch.
451  * Update *subsearch to remove any satisfied conditions.
452  */
subsearch_ok(constant char ** sp,constant char ** ep,int * subsearch)453 static lbool subsearch_ok(constant char **sp, constant char **ep, int *subsearch)
454 {
455 	int i;
456 	if (*subsearch == 0)
457 		return TRUE;
458 	for (i = 1;  i <= NUM_SEARCH_COLORS;  i++)
459 	{
460 		if (ep[i] != sp[i]) /* i-th subpattern matched */
461 		{
462 			*subsearch &= ~SRCH_SUBSEARCH(i);
463 			if (*subsearch == 0)
464 				return TRUE;
465 		}
466 	}
467 	return FALSE;
468 }
469 
match_pattern(PATTERN_TYPE pattern,constant char * tpattern,constant char * line,size_t line_len,size_t line_off,constant char ** sp,constant char ** ep,int nsp,int notbol,int search_type)470 public lbool match_pattern(PATTERN_TYPE pattern, constant char *tpattern, constant char *line, size_t line_len, size_t line_off, constant char **sp, constant char **ep, int nsp, int notbol, int search_type)
471 {
472 	int subsearch = search_type & SRCH_SUBSEARCH_ALL;
473 	for (;;)
474 	{
475 		size_t mlen;
476 		lbool matched = match_pattern1(pattern, tpattern, line, line_len, line_off, sp, ep, nsp, notbol, search_type);
477 		if (!matched || subsearch_ok(sp, ep, &subsearch))
478 			return matched;
479 		/* We have a match, but it does not satisfy all SUBSEARCH conditions.
480 		 * Continue searching after this match. */
481 		mlen = ptr_diff(ep[0], line);
482 		if (mlen == 0)
483 			/* If the match is empty, we can't progress. */
484 			return FALSE;
485 		line += mlen;
486 		line_len -= mlen;
487 		notbol = 1;
488 	}
489 }
490 
491 /*
492  * Return the name of the pattern matching library.
493  */
pattern_lib_name(void)494 public constant char * pattern_lib_name(void)
495 {
496 #if HAVE_GNU_REGEX
497 	return ("GNU");
498 #else
499 #if HAVE_POSIX_REGCOMP
500 	return ("POSIX");
501 #else
502 #if HAVE_PCRE2
503 	return ("PCRE2");
504 #else
505 #if HAVE_PCRE
506 	return ("PCRE");
507 #else
508 #if HAVE_RE_COMP
509 	return ("BSD");
510 #else
511 #if HAVE_REGCMP
512 	return ("V8");
513 #else
514 #if HAVE_V8_REGCOMP
515 	return ("Spencer V8");
516 #else
517 	return ("no");
518 #endif
519 #endif
520 #endif
521 #endif
522 #endif
523 #endif
524 #endif
525 }
526