util/stringops: add strmatch() and strnmatch()

Primitive "glob"-style pattern matching functions.
This commit is contained in:
Andrei Alexeyev 2022-08-27 03:14:00 +03:00
parent e1b1fecaf2
commit dca1aefbd8
No known key found for this signature in database
GPG key ID: 72D26128040B9690
2 changed files with 88 additions and 0 deletions

View file

@ -452,3 +452,80 @@ void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_
}
}
#endif
bool strnmatch(size_t globsize, const char glob[globsize], size_t insize, const char input[insize]) {
const char *ip = input;
const char *iend = input + insize;
const char *gp = glob;
const char *gend = glob + globsize;
int isfirst = 1;
int islast = 0;
for(;;) {
assert(!islast);
if(gp > gend) {
break;
}
const char *segment_start = gp;
const char *segment_end = memchr(segment_start, '*', gend - segment_start);
if(!segment_end) {
segment_end = gend;
islast = 1;
}
size_t segment_len = segment_end - segment_start;
if(segment_len < 1) {
if(islast) {
// Pattern ends with a * and we matched everything up to that point.
return 1;
}
} else {
// If this is the first segment, match at the start of input.
// Else search input for the first occurrence of the segment (lazy match).
if(isfirst) {
if(iend - ip < segment_len) {
// input shorter than pattern; can't possibly match
return 0;
}
if(memcmp(ip, segment_start, segment_len)) {
return 0;
}
} else if(!(ip = memmem(ip, iend - ip, segment_start, segment_len))) {
return 0;
}
ip += segment_len;
}
isfirst = 0;
gp += segment_len + 1;
if(islast) {
if(iend - ip == 0) {
// perfect match
return 1;
}
// Try to match the last pattern "greedily"
// So that e.g. a pattern like "start_*_end" can match "start_123_end_123_end"
if(iend - ip >= segment_len && !memcmp(iend - segment_len, segment_start, segment_len)) {
return 1;
}
return 0;
}
}
return 1;
}
bool strmatch(const char *glob, const char *input) {
return strnmatch(strlen(glob), glob, strlen(input), input);
}

View file

@ -82,3 +82,14 @@ void hexdigest(uint8_t *input, size_t input_size, char *output, size_t output_si
#define FILENAME_TIMESTAMP_MIN_BUF_SIZE 23
size_t filename_timestamp(char *buf, size_t buf_size, const SystemTime time) attr_nonnull(1);
/*
* Test if string matches a simple 'glob'-like pattern.
*
* Only '*' is supported as a metacharacter in the glob.
* '*' matches zero or more characters lazily.
* If the glob is not empty and does not end with a *, the last segment is matched greedily.
* An empty glob matches everything.
*/
bool strnmatch(size_t globsize, const char glob[globsize], size_t insize, const char input[insize]);
bool strmatch(const char *glob, const char *input);