diff --git a/docs/README.md b/docs/README.md index 4fdecc63..9dacf7a5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2273,13 +2273,17 @@ if (mg_vcmp(str, "Hello, world") == 0) { } ``` -### mg\_globmatch() +### mg\_match() ```c -bool mg_globmatch(const char *pattern, size_t p_len, const char *s, size_t s_len); +bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps); ``` -Check if string `s` (limited to `s_len` symbols) matches glob pattern `pattern`, (limited to `p_len` symbols). +Check if string `str` matches glob pattern `pattern`, and optionally capture +wildcards into the provided array `caps`. +NOTE: If `caps` is not NULL, then it +`caps` array size must be at least the number of wildcard symbols in `pattern` +plus 1. Last cap will be initialized to an empty string. The glob pattern matching rules are as follows: - `?` matches any single character @@ -2288,21 +2292,19 @@ The glob pattern matching rules are as follows: - any other character matches itself Parameters: -- `pattern` - Pattern to match for -- `p_len` - Pattern length -- `s` - String to match -- `s_len` - String length +- `str` - a string to match +- `pattern` - a pattern to match against +- `caps` - an optional array of captures for wildcard symbols `?`, `*`, '#' Return value: `true` if matches, `false` otherwise Usage example: ```c -struct mg_str pattern = mg_str("#, ?????"); -struct mg_str s = mg_str("Hello, world"); - -if (mg_globmatch(pattern.ptr, pattern.len, s.otr, s.len)) { - // Match +// Assume that hm->uri holds /foo/bar. Then we can match the requested URI: +struct mg_str caps[2]; +if (mg_match(hm->uri, mg_str("/*/*"))) { + // caps[0] holds `foo`, caps[1] holds `bar`. } ``` diff --git a/examples/mqtt-server/main.c b/examples/mqtt-server/main.c index a9d12a09..a7c16a5b 100644 --- a/examples/mqtt-server/main.c +++ b/examples/mqtt-server/main.c @@ -59,7 +59,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { LIST_ADD_HEAD(struct sub, &s_subs, sub); LOG(LL_INFO, ("SUB %p [%.*s]", c->fd, (int) sub->topic.len, sub->topic.ptr)); - // Change '+' to '*' for topic matching using mg_globmatch + // Change '+' to '*' for topic matching using mg_match for (size_t i = 0; i < sub->topic.len; i++) { if (sub->topic.ptr[i] == '+') ((char *) sub->topic.ptr)[i] = '*'; } @@ -76,8 +76,7 @@ static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { LOG(LL_INFO, ("PUB %p [%.*s] -> [%.*s]", c->fd, (int) mm->data.len, mm->data.ptr, (int) mm->topic.len, mm->topic.ptr)); for (struct sub *sub = s_subs; sub != NULL; sub = sub->next) { - if (mg_globmatch(sub->topic.ptr, sub->topic.len, mm->topic.ptr, - mm->topic.len)) { + if (mg_match(mm->topic, sub->topic, NULL)) { mg_mqtt_pub(sub->c, mm->topic, mm->data, 1, false); } } diff --git a/mongoose.c b/mongoose.c index d5f02b03..fd48b455 100644 --- a/mongoose.c +++ b/mongoose.c @@ -1760,7 +1760,7 @@ struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { } bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { - return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len); + return mg_match(hm->uri, mg_str(glob), NULL); } static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) { @@ -3870,6 +3870,65 @@ struct mg_str mg_strstrip(struct mg_str s) { return s; } +bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) { + size_t i = 0, j = 0, ni = 0, nj = 0; + if (caps) caps->ptr = NULL, caps->len = 0; + while (i < p.len || j < s.len) { + if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] == p.ptr[i])) { + if (caps == NULL) { + } else if (p.ptr[i] == '?') { + caps->ptr = &s.ptr[j], caps->len = 1; // Finalize `?` cap + caps++, caps->ptr = NULL, caps->len = 0; // Init next cap + } else if (caps->ptr != NULL && caps->len == 0) { + caps->len = (size_t) (&s.ptr[j] - caps->ptr); // Finalize current cap + caps++, caps->len = 0, caps->ptr = NULL; // Init next cap + } + i++, j++; + } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) { + if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j]; // Init cap + ni = i++, nj = j + 1; + } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' || s.ptr[j] != '/')) { + i = ni, j = nj; + if (caps && caps->ptr == NULL && caps->len == 0) { + caps--, caps->len = 0; // Restart previous cap + } + } else { + return false; + } + } + if (caps && caps->ptr && caps->len == 0) { + caps->len = (size_t) (&s.ptr[j] - caps->ptr); + } + return true; +} + +bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) { + return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL); +} + +static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff, + size_t *klen, size_t *voff, size_t *vlen) { + size_t kvlen, kl; + for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != ',';) kvlen++; + for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++; + if (koff != NULL) *koff = ofs; + if (klen != NULL) *klen = kl; + if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0; + if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0; + ofs += kvlen + 1; + return ofs > n ? n : ofs; +} + +bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) { + size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0; + if (s->ptr == NULL || s->len == 0) return 0; + off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen); + if (k != NULL) *k = mg_str_n(s->ptr + koff, klen); + if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen); + *s = mg_str_n(s->ptr + off, s->len - off); + return off > 0; +} + #ifdef MG_ENABLE_LINES #line 1 "src/timer.c" #endif @@ -4421,46 +4480,6 @@ void mg_random(void *buf, size_t len) { } #endif -bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) { - size_t i = 0, j = 0, ni = 0, nj = 0; - while (i < n1 || j < n2) { - if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) { - i++, j++; - } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) { - ni = i, nj = j + 1, i++; - } else if (nj > 0 && nj <= n2 && (s1[ni] == '#' || s2[j] != '/')) { - i = ni, j = nj; - } else { - // printf(">>: [%s] [%s] %d %d %d %d\n", s1, s2, i, j, ni, nj); - return false; - } - } - return true; -} - -static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff, - size_t *klen, size_t *voff, size_t *vlen) { - size_t kvlen, kl; - for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != ',';) kvlen++; - for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++; - if (koff != NULL) *koff = ofs; - if (klen != NULL) *klen = kl; - if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0; - if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0; - ofs += kvlen + 1; - return ofs > n ? n : ofs; -} - -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) { - size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0; - if (s->ptr == NULL || s->len == 0) return 0; - off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen); - if (k != NULL) *k = mg_str_n(s->ptr + koff, klen); - if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen); - *s = mg_str_n(s->ptr + off, s->len - off); - return off > 0; -} - uint32_t mg_ntohl(uint32_t net) { uint8_t data[4] = {0, 0, 0, 0}; memcpy(&data, &net, sizeof(data)); diff --git a/mongoose.h b/mongoose.h index 917f1a3d..ec25c709 100644 --- a/mongoose.h +++ b/mongoose.h @@ -580,6 +580,9 @@ int mg_strcmp(const struct mg_str str1, const struct mg_str str2); struct mg_str mg_strstrip(struct mg_str s); struct mg_str mg_strdup(const struct mg_str s); const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); +bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps); +bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n); +bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v); @@ -676,8 +679,6 @@ bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...); void mg_random(void *buf, size_t len); -bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n); -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v); uint16_t mg_ntohs(uint16_t net); uint32_t mg_ntohl(uint32_t net); uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); diff --git a/src/http.c b/src/http.c index 79dec1b9..186142d1 100644 --- a/src/http.c +++ b/src/http.c @@ -808,7 +808,7 @@ struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v) { } bool mg_http_match_uri(const struct mg_http_message *hm, const char *glob) { - return mg_globmatch(glob, strlen(glob), hm->uri.ptr, hm->uri.len); + return mg_match(hm->uri, mg_str(glob), NULL); } static size_t get_chunk_length(const char *buf, size_t len, size_t *ll) { diff --git a/src/str.c b/src/str.c index 4f95c80c..78646dd0 100644 --- a/src/str.c +++ b/src/str.c @@ -86,3 +86,62 @@ struct mg_str mg_strstrip(struct mg_str s) { while (s.len > 0 && isspace((int) *(s.ptr + s.len - 1))) s.len--; return s; } + +bool mg_match(struct mg_str s, struct mg_str p, struct mg_str *caps) { + size_t i = 0, j = 0, ni = 0, nj = 0; + if (caps) caps->ptr = NULL, caps->len = 0; + while (i < p.len || j < s.len) { + if (i < p.len && j < s.len && (p.ptr[i] == '?' || s.ptr[j] == p.ptr[i])) { + if (caps == NULL) { + } else if (p.ptr[i] == '?') { + caps->ptr = &s.ptr[j], caps->len = 1; // Finalize `?` cap + caps++, caps->ptr = NULL, caps->len = 0; // Init next cap + } else if (caps->ptr != NULL && caps->len == 0) { + caps->len = (size_t) (&s.ptr[j] - caps->ptr); // Finalize current cap + caps++, caps->len = 0, caps->ptr = NULL; // Init next cap + } + i++, j++; + } else if (i < p.len && (p.ptr[i] == '*' || p.ptr[i] == '#')) { + if (caps && !caps->ptr) caps->len = 0, caps->ptr = &s.ptr[j]; // Init cap + ni = i++, nj = j + 1; + } else if (nj > 0 && nj <= s.len && (p.ptr[ni] == '#' || s.ptr[j] != '/')) { + i = ni, j = nj; + if (caps && caps->ptr == NULL && caps->len == 0) { + caps--, caps->len = 0; // Restart previous cap + } + } else { + return false; + } + } + if (caps && caps->ptr && caps->len == 0) { + caps->len = (size_t) (&s.ptr[j] - caps->ptr); + } + return true; +} + +bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) { + return mg_match(mg_str_n(s2, n2), mg_str_n(s1, n1), NULL); +} + +static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff, + size_t *klen, size_t *voff, size_t *vlen) { + size_t kvlen, kl; + for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != ',';) kvlen++; + for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++; + if (koff != NULL) *koff = ofs; + if (klen != NULL) *klen = kl; + if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0; + if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0; + ofs += kvlen + 1; + return ofs > n ? n : ofs; +} + +bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) { + size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0; + if (s->ptr == NULL || s->len == 0) return 0; + off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen); + if (k != NULL) *k = mg_str_n(s->ptr + koff, klen); + if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen); + *s = mg_str_n(s->ptr + off, s->len - off); + return off > 0; +} diff --git a/src/str.h b/src/str.h index 494d7317..658fda0f 100644 --- a/src/str.h +++ b/src/str.h @@ -27,3 +27,6 @@ int mg_strcmp(const struct mg_str str1, const struct mg_str str2); struct mg_str mg_strstrip(struct mg_str s); struct mg_str mg_strdup(const struct mg_str s); const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); +bool mg_match(struct mg_str str, struct mg_str pattern, struct mg_str *caps); +bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n); +bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v); diff --git a/src/util.c b/src/util.c index 9d6ff65e..91a0a556 100644 --- a/src/util.c +++ b/src/util.c @@ -25,46 +25,6 @@ void mg_random(void *buf, size_t len) { } #endif -bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) { - size_t i = 0, j = 0, ni = 0, nj = 0; - while (i < n1 || j < n2) { - if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) { - i++, j++; - } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) { - ni = i, nj = j + 1, i++; - } else if (nj > 0 && nj <= n2 && (s1[ni] == '#' || s2[j] != '/')) { - i = ni, j = nj; - } else { - // printf(">>: [%s] [%s] %d %d %d %d\n", s1, s2, i, j, ni, nj); - return false; - } - } - return true; -} - -static size_t mg_nce(const char *s, size_t n, size_t ofs, size_t *koff, - size_t *klen, size_t *voff, size_t *vlen) { - size_t kvlen, kl; - for (kvlen = 0; ofs + kvlen < n && s[ofs + kvlen] != ',';) kvlen++; - for (kl = 0; kl < kvlen && s[ofs + kl] != '=';) kl++; - if (koff != NULL) *koff = ofs; - if (klen != NULL) *klen = kl; - if (voff != NULL) *voff = kl < kvlen ? ofs + kl + 1 : 0; - if (vlen != NULL) *vlen = kl < kvlen ? kvlen - kl - 1 : 0; - ofs += kvlen + 1; - return ofs > n ? n : ofs; -} - -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v) { - size_t koff = 0, klen = 0, voff = 0, vlen = 0, off = 0; - if (s->ptr == NULL || s->len == 0) return 0; - off = mg_nce(s->ptr, s->len, 0, &koff, &klen, &voff, &vlen); - if (k != NULL) *k = mg_str_n(s->ptr + koff, klen); - if (v != NULL) *v = mg_str_n(s->ptr + voff, vlen); - *s = mg_str_n(s->ptr + off, s->len - off); - return off > 0; -} - uint32_t mg_ntohl(uint32_t net) { uint8_t data[4] = {0, 0, 0, 0}; memcpy(&data, &net, sizeof(data)); diff --git a/src/util.h b/src/util.h index 570b86ae..e4943ecc 100644 --- a/src/util.h +++ b/src/util.h @@ -5,8 +5,6 @@ #include "str.h" void mg_random(void *buf, size_t len); -bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n); -bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v); uint16_t mg_ntohs(uint16_t net); uint32_t mg_ntohl(uint32_t net); uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); diff --git a/test/unit_test.c b/test/unit_test.c index 3063c7fe..04d5d2ce 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -48,6 +48,47 @@ static void test_globmatch(void) { ASSERT(mg_globmatch("#.shtml", 7, "./ssi/index.shtml", 17) == 1); ASSERT(mg_globmatch("#aa#bb#", 7, "caabba", 6) == 1); ASSERT(mg_globmatch("#aa#bb#", 7, "caabxa", 6) == 0); + ASSERT(mg_globmatch("a*b*c", 5, "a__b_c", 6) == 1); + + { + struct mg_str caps[3]; + ASSERT(mg_match(mg_str("//a.c"), mg_str("#.c"), NULL) == true); + ASSERT(mg_match(mg_str("a"), mg_str("#"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("a")) == 0); + ASSERT(mg_match(mg_str("//a.c"), mg_str("#.c"), caps) == true); + ASSERT(mg_match(mg_str("a_b_c_"), mg_str("a*b*c"), caps) == false); + ASSERT(mg_match(mg_str("a__b_c"), mg_str("a*b*c"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("__")) == 0); + ASSERT(mg_strcmp(caps[1], mg_str("_")) == 0); + ASSERT(mg_match(mg_str("a_b_c__c"), mg_str("a*b*c"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("_")) == 0); + ASSERT(mg_strcmp(caps[1], mg_str("_c__")) == 0); + ASSERT(mg_match(mg_str("a_xb_.c__c"), mg_str("a*b*c"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("_x")) == 0); + ASSERT(mg_strcmp(caps[1], mg_str("_.c__")) == 0); + ASSERT(mg_match(mg_str("a"), mg_str("#a"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("")) == 0); + + ASSERT(mg_match(mg_str(".aa..b...b"), mg_str("#a#b"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str(".")) == 0); + ASSERT(mg_strcmp(caps[1], mg_str("a..b...")) == 0); + ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); + + ASSERT(mg_match(mg_str("/foo/bar"), mg_str("/*/*"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("foo")) == 0); + ASSERT(mg_strcmp(caps[1], mg_str("bar")) == 0); + ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); + + ASSERT(mg_match(mg_str("/foo/"), mg_str("/*/*"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("foo")) == 0); + ASSERT(mg_strcmp(caps[1], mg_str("")) == 0); + ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); + + ASSERT(mg_match(mg_str("abc"), mg_str("?#"), caps) == true); + ASSERT(mg_strcmp(caps[0], mg_str("a")) == 0); + ASSERT(mg_strcmp(caps[1], mg_str("bc")) == 0); + ASSERT(mg_strcmp(caps[2], mg_str("")) == 0); + } } static void test_commalist(void) { @@ -1655,6 +1696,7 @@ static void test_get_header_var(void) { int main(void) { mg_log_set("3"); + test_globmatch(); test_get_header_var(); test_rewrites(); test_check_ip_acl(); @@ -1674,7 +1716,6 @@ int main(void) { test_iobuf(); test_commalist(); test_base64(); - test_globmatch(); test_http_get_var(); test_tls(); test_ws();