Move patternmatch from common.c

This commit is contained in:
Vladimir Dubrovin 2026-08-27 17:27:50 +03:00
parent 9529a1dfcf
commit 8971fcf991
2 changed files with 241 additions and 238 deletions

241
src/acl.c
View File

@ -8,6 +8,247 @@
#include "proxy.h" #include "proxy.h"
/* The pattern engine lives here rather than in common.c: common.c is linked
into the standalone binaries as well, and those carry neither the regular
expression code this calls nor a use for a host pattern. */
/* Host lists in access rules have always accepted name, name*, *name and
*name*, with the leading and trailing star recorded as a match type rather
than kept in the string. The parser and the comparison are here so that
anything else matching a name against a pattern - the http command, and
whatever replaces the star with a regular expression later - behaves the same
way and gains the same syntax at the same time.
*/
/* A pattern written as a regular expression carries a prefix. Both spellings
are taken so a configuration reads the way its author thinks of it. */
static unsigned char * regexprefix(unsigned char *arg)
{
if(!strncmp((char *)arg, "pcre:", 5)) return arg + 5;
if(!strncmp((char *)arg, "regex:", 6)) return arg + 6;
return NULL;
}
/* Shared by every pattern the configuration can carry. Returns 0 on success. */
static int compileregex(struct hostname *h, unsigned char *pattern)
{
#ifdef WITH_PCRE
char err[256];
h->re = pcre_pattern_compile(pattern, err, sizeof(err));
if(!h->re){
fprintf(stderr, "Bad regular expression '%s': %s\n", pattern, err);
return 1;
}
h->matchtype = MATCHREGEX;
h->name = (unsigned char *)strdup((char *)pattern);
return h->name? 0 : 1;
#else
fprintf(stderr, "Regular expression '%s' needs a build with PCRE\n", pattern);
return 1;
#endif
}
int parsepattern(struct hostname *h, unsigned char *arg)
{
int arglen;
unsigned char *pattern;
h->re = NULL;
if((pattern = regexprefix(arg))) return compileregex(h, pattern);
arglen = (int)strlen((char *)arg);
h->matchtype = 3;
pattern = arg;
if(arglen && pattern[arglen-1] == '*'){
arglen--;
pattern[arglen] = 0;
h->matchtype ^= MATCHEND;
}
if(arglen && pattern[0] == '*'){
pattern++;
arglen--;
h->matchtype ^= MATCHBEGIN;
}
h->name = (unsigned char *)strdup((char *)pattern);
return h->name? 0 : 1;
}
/* Matches str against a pattern and reports the part a star stood for. Where a
pattern has a star at both ends the trailing one is reported, since that is
the part following the text that was matched. An exact pattern leaves an
empty span. */
int patternmatchpos(const struct hostname *h, const unsigned char *str, int *start, int *len)
{
int lname, lstr, pos = 0, match = 0;
char *found;
if(!h->name || !str) return 0;
if(h->matchtype == MATCHREGEX || h->matchtype == MATCHGLOB){
struct capture caps[MAXCAPTURES];
if(!patternmatchcaps(h, str, caps, NULL)) return 0;
if(start) *start = caps[1].start;
if(len) *len = caps[1].len;
return 1;
}
lname = (int)strlen((char *)h->name);
lstr = (int)strlen((char *)str);
switch(h->matchtype){
case 0:
#ifndef _WIN32
found = strcasestr((char *)str, (char *)h->name);
#else
found = strstr((char *)str, (char *)h->name);
#endif
if(found){
match = 1;
pos = (int)(found - (char *)str) + lname;
}
break;
case 1:
if(!strncasecmp((char *)str, (char *)h->name, lname)){
match = 1;
pos = lname;
}
break;
case 2:
if(lstr >= lname &&
!strncasecmp((char *)str + (lstr - lname), (char *)h->name, lname)){
match = 1;
pos = 0;
if(start) *start = 0;
if(len) *len = lstr - lname;
return 1;
}
break;
default:
if(!strcasecmp((char *)str, (char *)h->name)){
match = 1;
pos = lstr;
}
break;
}
if(!match) return 0;
if(start) *start = pos;
if(len) *len = lstr - pos;
return 1;
}
int patternmatch(const struct hostname *h, const unsigned char *str)
{
return patternmatchcaps(h, str, NULL, NULL);
}
/* Match a glob, recording what each star stood for.
A single star stands for any run of characters within one element of the
path, so it stops at a slash; a double star crosses them. Stars are
numbered in the order they appear, which is how a template refers to them.
*/
static int globmatch(const unsigned char *pat, const unsigned char *str,
const unsigned char *subject, struct capture *caps, int maxcaps, int star)
{
while(*pat){
if(*pat == '*'){
int crosses = (pat[1] == '*');
const unsigned char *rest = pat + (crosses? 2 : 1);
int len;
for(len = 0; ; len++){
if(star < maxcaps && caps){
caps[star].start = (int)(str - subject);
caps[star].len = len;
}
if(globmatch(rest, str + len, subject, caps, maxcaps, star + 1)) return 1;
if(!str[len]) return 0;
if(!crosses && str[len] == '/') return 0;
}
}
if(*pat != *str) return 0;
pat++;
str++;
}
return *str == 0;
}
/* Match a pattern of any kind and report what its stars or groups stood for.
caps may be NULL when only the yes or no answer is wanted. */
int patternmatchcaps(const struct hostname *h, const unsigned char *str,
struct capture *caps, int *ncaps)
{
int n = 0;
if(ncaps) *ncaps = 0;
if(!h || !str) return 0;
if(h->matchtype == MATCHREGEX){
#ifdef WITH_PCRE
struct capture local[MAXCAPTURES];
n = pcre_pattern_match(h->re, str, caps? caps : local, MAXCAPTURES);
if(ncaps) *ncaps = n;
return n > 0;
#else
return 0;
#endif
}
if(h->matchtype == MATCHGLOB){
struct capture local[MAXCAPTURES];
struct capture *use = caps? caps : local;
int i;
for(i = 0; i < MAXCAPTURES; i++){
use[i].start = 0;
use[i].len = 0;
}
use[0].start = 0;
use[0].len = (int)strlen((char *)str);
if(!h->name) return 0;
if(!globmatch(h->name, str, str, use, MAXCAPTURES, 1)) return 0;
if(ncaps){
for(n = MAXCAPTURES - 1; n > 0 && !use[n].len && !use[n].start; n--);
*ncaps = n + 1;
}
return 1;
}
/* the star at one end or both, as an access rule has always written it */
if(caps){
int start = 0, len = 0;
if(!patternmatchpos(h, str, &start, &len)) return 0;
caps[0].start = 0;
caps[0].len = (int)strlen((char *)str);
caps[1].start = start;
caps[1].len = len;
if(ncaps) *ncaps = 2;
return 1;
}
return patternmatchpos(h, str, NULL, NULL);
}
/* A URL in an http rule: stars anywhere, or a regular expression. */
int parsepathpattern(struct hostname *h, unsigned char *arg)
{
unsigned char *pattern;
h->re = NULL;
if((pattern = regexprefix(arg))) return compileregex(h, pattern);
h->matchtype = MATCHGLOB;
h->name = (unsigned char *)strdup((char *)arg);
return h->name? 0 : 1;
}
int IPInentry(struct sockaddr *sa, struct iplist *ipentry){ int IPInentry(struct sockaddr *sa, struct iplist *ipentry){
int addrlen; int addrlen;
unsigned char *ip, *ipf, *ipt; unsigned char *ip, *ipf, *ipt;

View File

@ -810,244 +810,6 @@ int bindwithrange(struct clientparam *param, SOCKET sock, PROXYSOCKADDRTYPE *sa,
return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa)); return param->srv->so._bind(param->sostate, sock, (struct sockaddr *)sa, SASIZE(sa));
} }
/* Host lists in access rules have always accepted name, name*, *name and
*name*, with the leading and trailing star recorded as a match type rather
than kept in the string. The parser and the comparison are here so that
anything else matching a name against a pattern - the http command, and
whatever replaces the star with a regular expression later - behaves the same
way and gains the same syntax at the same time.
*/
/* A pattern written as a regular expression carries a prefix. Both spellings
are taken so a configuration reads the way its author thinks of it. */
static unsigned char * regexprefix(unsigned char *arg)
{
if(!strncmp((char *)arg, "pcre:", 5)) return arg + 5;
if(!strncmp((char *)arg, "regex:", 6)) return arg + 6;
return NULL;
}
/* Shared by every pattern the configuration can carry. Returns 0 on success. */
static int compileregex(struct hostname *h, unsigned char *pattern)
{
#ifdef WITH_PCRE
char err[256];
h->re = pcre_pattern_compile(pattern, err, sizeof(err));
if(!h->re){
fprintf(stderr, "Bad regular expression '%s': %s\n", pattern, err);
return 1;
}
h->matchtype = MATCHREGEX;
h->name = (unsigned char *)strdup((char *)pattern);
return h->name? 0 : 1;
#else
fprintf(stderr, "Regular expression '%s' needs a build with PCRE\n", pattern);
return 1;
#endif
}
int parsepattern(struct hostname *h, unsigned char *arg)
{
int arglen;
unsigned char *pattern;
h->re = NULL;
if((pattern = regexprefix(arg))) return compileregex(h, pattern);
arglen = (int)strlen((char *)arg);
h->matchtype = 3;
pattern = arg;
if(arglen && pattern[arglen-1] == '*'){
arglen--;
pattern[arglen] = 0;
h->matchtype ^= MATCHEND;
}
if(arglen && pattern[0] == '*'){
pattern++;
arglen--;
h->matchtype ^= MATCHBEGIN;
}
h->name = (unsigned char *)strdup((char *)pattern);
return h->name? 0 : 1;
}
/* Matches str against a pattern and reports the part a star stood for. Where a
pattern has a star at both ends the trailing one is reported, since that is
the part following the text that was matched. An exact pattern leaves an
empty span. */
int patternmatchpos(const struct hostname *h, const unsigned char *str, int *start, int *len)
{
int lname, lstr, pos = 0, match = 0;
char *found;
if(!h->name || !str) return 0;
if(h->matchtype == MATCHREGEX || h->matchtype == MATCHGLOB){
struct capture caps[MAXCAPTURES];
if(!patternmatchcaps(h, str, caps, NULL)) return 0;
if(start) *start = caps[1].start;
if(len) *len = caps[1].len;
return 1;
}
lname = (int)strlen((char *)h->name);
lstr = (int)strlen((char *)str);
switch(h->matchtype){
case 0:
#ifndef _WIN32
found = strcasestr((char *)str, (char *)h->name);
#else
found = strstr((char *)str, (char *)h->name);
#endif
if(found){
match = 1;
pos = (int)(found - (char *)str) + lname;
}
break;
case 1:
if(!strncasecmp((char *)str, (char *)h->name, lname)){
match = 1;
pos = lname;
}
break;
case 2:
if(lstr >= lname &&
!strncasecmp((char *)str + (lstr - lname), (char *)h->name, lname)){
match = 1;
pos = 0;
if(start) *start = 0;
if(len) *len = lstr - lname;
return 1;
}
break;
default:
if(!strcasecmp((char *)str, (char *)h->name)){
match = 1;
pos = lstr;
}
break;
}
if(!match) return 0;
if(start) *start = pos;
if(len) *len = lstr - pos;
return 1;
}
int patternmatch(const struct hostname *h, const unsigned char *str)
{
return patternmatchcaps(h, str, NULL, NULL);
}
/* Match a glob, recording what each star stood for.
A single star stands for any run of characters within one element of the
path, so it stops at a slash; a double star crosses them. Stars are
numbered in the order they appear, which is how a template refers to them.
*/
static int globmatch(const unsigned char *pat, const unsigned char *str,
const unsigned char *subject, struct capture *caps, int maxcaps, int star)
{
while(*pat){
if(*pat == '*'){
int crosses = (pat[1] == '*');
const unsigned char *rest = pat + (crosses? 2 : 1);
int len;
for(len = 0; ; len++){
if(star < maxcaps && caps){
caps[star].start = (int)(str - subject);
caps[star].len = len;
}
if(globmatch(rest, str + len, subject, caps, maxcaps, star + 1)) return 1;
if(!str[len]) return 0;
if(!crosses && str[len] == '/') return 0;
}
}
if(*pat != *str) return 0;
pat++;
str++;
}
return *str == 0;
}
/* Match a pattern of any kind and report what its stars or groups stood for.
caps may be NULL when only the yes or no answer is wanted. */
int patternmatchcaps(const struct hostname *h, const unsigned char *str,
struct capture *caps, int *ncaps)
{
int n = 0;
if(ncaps) *ncaps = 0;
if(!h || !str) return 0;
if(h->matchtype == MATCHREGEX){
#ifdef WITH_PCRE
struct capture local[MAXCAPTURES];
n = pcre_pattern_match(h->re, str, caps? caps : local, MAXCAPTURES);
if(ncaps) *ncaps = n;
return n > 0;
#else
return 0;
#endif
}
if(h->matchtype == MATCHGLOB){
struct capture local[MAXCAPTURES];
struct capture *use = caps? caps : local;
int i;
for(i = 0; i < MAXCAPTURES; i++){
use[i].start = 0;
use[i].len = 0;
}
use[0].start = 0;
use[0].len = (int)strlen((char *)str);
if(!h->name) return 0;
if(!globmatch(h->name, str, str, use, MAXCAPTURES, 1)) return 0;
if(ncaps){
for(n = MAXCAPTURES - 1; n > 0 && !use[n].len && !use[n].start; n--);
*ncaps = n + 1;
}
return 1;
}
/* the star at one end or both, as an access rule has always written it */
if(caps){
int start = 0, len = 0;
if(!patternmatchpos(h, str, &start, &len)) return 0;
caps[0].start = 0;
caps[0].len = (int)strlen((char *)str);
caps[1].start = start;
caps[1].len = len;
if(ncaps) *ncaps = 2;
return 1;
}
return patternmatchpos(h, str, NULL, NULL);
}
/* A URL in an http rule: stars anywhere, or a regular expression. */
int parsepathpattern(struct hostname *h, unsigned char *arg)
{
unsigned char *pattern;
h->re = NULL;
if((pattern = regexprefix(arg))) return compileregex(h, pattern);
h->matchtype = MATCHGLOB;
h->name = (unsigned char *)strdup((char *)arg);
return h->name? 0 : 1;
}
int scanaddr(const unsigned char *s, uint32_t * ip, uint32_t * mask) { int scanaddr(const unsigned char *s, uint32_t * ip, uint32_t * mask) {
unsigned d1, d2, d3, d4, m; unsigned d1, d2, d3, d4, m;
int res; int res;