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("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("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("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[ecount-1].rm_so >= 0) 347 break; 348 if (ecount >= nsp) 349 ecount = nsp-1; 350 for (i = 0; i < ecount; i++) 351 { 352 if (rm[i].rm_so < 0) 353 { 354 *sp++ = *ep++ = line; 355 } else 356 { 357 #ifndef __WATCOMC__ 358 *sp++ = line + rm[i].rm_so; 359 *ep++ = line + rm[i].rm_eo; 360 #else 361 *sp++ = rm[i].rm_sp; 362 *ep++ = rm[i].rm_ep; 363 #endif 364 } 365 } 366 } 367 } 368 #endif 369 #if HAVE_PCRE 370 { 371 #define OVECTOR_COUNT ((3*NUM_SEARCH_COLORS)+3) 372 int ovector[OVECTOR_COUNT]; 373 int flags = (notbol) ? PCRE_NOTBOL : 0; 374 int i; 375 int ecount; 376 int mcount = pcre_exec(pattern, NULL, line, line_len, 377 line_off, flags, ovector, OVECTOR_COUNT); 378 matched = (mcount > 0); 379 ecount = nsp-1; 380 if (ecount > mcount) ecount = mcount; 381 for (i = 0; i < ecount*2; ) 382 { 383 if (ovector[i] < 0 || ovector[i+1] < 0) 384 { 385 *sp++ = *ep++ = line; 386 i += 2; 387 } else 388 { 389 *sp++ = line + ovector[i++]; 390 *ep++ = line + ovector[i++]; 391 } 392 } 393 } 394 #endif 395 #if HAVE_PCRE2 396 { 397 int flags = (notbol) ? PCRE2_NOTBOL : 0; 398 pcre2_match_data *md = pcre2_match_data_create_from_pattern(pattern, NULL); 399 int mcount = pcre2_match(pattern, (PCRE2_SPTR)line, line_len, 400 line_off, flags, md, NULL); 401 matched = (mcount > 0); 402 if (matched) 403 { 404 PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(md); 405 int i; 406 int ecount = nsp-1; 407 if (ecount > mcount) ecount = mcount; 408 for (i = 0; i < ecount*2; ) 409 { 410 if (ovector[i] < 0 || ovector[i+1] < 0) 411 { 412 *sp++ = *ep++ = line; 413 i += 2; 414 } else 415 { 416 *sp++ = line + ovector[i++]; 417 *ep++ = line + ovector[i++]; 418 } 419 } 420 } 421 pcre2_match_data_free(md); 422 } 423 #endif 424 #if HAVE_RE_COMP 425 matched = (re_exec(line + line_off) == 1); 426 /* 427 * re_exec doesn't seem to provide a way to get the matched string. 428 */ 429 #endif 430 #if HAVE_REGCMP 431 matched = ((*ep++ = regex(pattern, line + line_off)) != NULL); 432 if (matched) 433 *sp++ = __loc1; 434 #endif 435 #if HAVE_V8_REGCOMP 436 #if HAVE_REGEXEC2 437 matched = regexec2(pattern, line + line_off, notbol); 438 #else 439 matched = regexec(pattern, line + line_off); 440 #endif 441 if (matched) 442 { 443 *sp++ = pattern->startp[0]; 444 *ep++ = pattern->endp[0]; 445 } 446 #endif 447 } 448 *sp = *ep = NULL; 449 matched = (!(search_type & SRCH_NO_MATCH) && matched) || 450 ((search_type & SRCH_NO_MATCH) && !matched); 451 return (matched != 0); 452 } 453 454 /* 455 * Return TRUE if the match satisfies all SUBSEARCH conditions. 456 */ 457 static lbool subsearch_ok(constant char **sp, constant char **ep, int search_type) 458 { 459 int i; 460 for (i = 1; i <= NUM_SEARCH_COLORS; i++) 461 { 462 if ((search_type & SRCH_SUBSEARCH(i)) && ep[i] == sp[i]) 463 return FALSE; 464 } 465 return TRUE; 466 } 467 468 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) 469 { 470 for (;;) 471 { 472 size_t mlen; 473 lbool matched = match_pattern1(pattern, tpattern, line, line_len, line_off, sp, ep, nsp, notbol, search_type); 474 if (!matched || subsearch_ok(sp, ep, search_type)) 475 return matched; 476 /* We have a match, but it does not satisfy all SUBSEARCH conditions. 477 * Continue searching after this match. */ 478 mlen = ptr_diff(ep[0], line); 479 if (mlen == 0) 480 /* If the match is empty, we can't progress. */ 481 return FALSE; 482 line += mlen; 483 line_len -= mlen; 484 notbol = 1; 485 } 486 } 487 488 /* 489 * Return the name of the pattern matching library. 490 */ 491 public constant char * pattern_lib_name(void) 492 { 493 #if HAVE_GNU_REGEX 494 return ("GNU"); 495 #else 496 #if HAVE_POSIX_REGCOMP 497 return ("POSIX"); 498 #else 499 #if HAVE_PCRE2 500 return ("PCRE2"); 501 #else 502 #if HAVE_PCRE 503 return ("PCRE"); 504 #else 505 #if HAVE_RE_COMP 506 return ("BSD"); 507 #else 508 #if HAVE_REGCMP 509 return ("V8"); 510 #else 511 #if HAVE_V8_REGCOMP 512 return ("Spencer V8"); 513 #else 514 return ("no"); 515 #endif 516 #endif 517 #endif 518 #endif 519 #endif 520 #endif 521 #endif 522 } 523