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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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