1 /*
2  Copyright (c) 2006 Aaron Stone <aaron@serendipity.cx>
3 
4  This program is free software; you can redistribute it and/or
5  modify it under the terms of the GNU General Public License
6  as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later
8  version.
9 
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  GNU General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program; if not, write to the Free Software
17  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 
20 #include "dbmail.h"
21 
weird_tokenize(char * pat,char * sep)22 static char **weird_tokenize(char *pat, char *sep)
23 {
24 	char **parts;
25 	char *pat_begin, *pat_end;
26 	int t = 0, tokens = 1;
27 
28 	// Count the number of separators in pat
29 	for (pat_end = pat; pat_end[0] != '\0'; pat_end++) {
30 		if (strchr(sep, pat_end[0])) {
31 			tokens+=2;
32 		}
33 	}
34 
35 	// Mask the original pat.
36 	pat = g_strdup(pat);
37 	parts = g_new0(char *, tokens + 1);
38 
39 	// Catch the degenerate case.
40 	if (tokens == 1) {
41 		parts[0] = pat;
42 		return parts;
43 	}
44 
45 	// Break pat by sep into parts.
46 	for (pat_begin = pat_end = pat; pat_end[0] != '\0'; pat_end++) {
47 		if (strchr(sep, pat_end[0])) {
48 			if (pat_begin == pat_end) {
49 				parts[t] = g_strdup(" ");
50 				parts[t][0] = pat_end[0];
51 				pat_end[0] = '\0';
52 				pat_begin++;
53 				t++;
54 			} else {
55 				parts[t+1] = g_strdup(" ");
56 				parts[t+1][0] = pat_end[0];
57 				pat_end[0] = '\0';
58 				parts[t] = g_strdup(pat_begin);
59 				pat_begin = pat_end + 1;
60 				t+=2;
61 			}
62 		}
63 	}
64 
65 	// Don't overwrite existing values
66 	// Copy the part if we haven't found a sep char.
67 	if (!parts[t] && pat_begin < pat_end) {
68 		parts[t] = g_strdup(pat_begin);
69 	}
70 
71 	g_free(pat);
72 
73 	return parts;
74 }
75 
76 
77 // See if the needle matches the haystack, given
78 // than '?' means zero or one character and '*'
79 // means zero or any number of characters.
80 //
81 // Returns the candidate argument if it matches, or NULL.
82 // Does not make a copy of candidate. This allows nesting.
match_glob(char * pattern,char * candidate)83 char *match_glob(char *pattern, char *candidate)
84 {
85 	char *can = candidate;
86 	char **parts;
87 	int i, has_star = 0, has_question = 0;
88 
89 	parts = weird_tokenize(pattern, "?*");
90 	if (!parts)
91 		return NULL;
92 
93 	// Each of the parts much be placed on candidate.
94 	for (i = 0; parts[i] != NULL; i++) {
95 		int len, plen;
96 
97 		plen = strlen(parts[i]);
98 
99 		if (parts[i][0] == '\0') {
100 			continue;
101 		}
102 
103 		if (parts[i][0] == '*') {
104 			// Signals that we should do an anywhere-ahead match.
105 			has_star = 1;
106 			continue;
107 		}
108 
109 		if (parts[i][0] == '?') {
110 			// Signals that we should do a here or next match.
111 			// Count up the number of spaces we're allowed to skip.
112 			has_question++;
113 			continue;
114 		}
115 
116 		len = strlen(can);
117 
118 		if (has_star) {
119 			int j;
120 
121 			for (j = 0; j < len; j++) {
122 				if (strncmp(parts[i], can + j, MIN(plen, len - j)) == 0) {
123 					can += CLAMP(plen + j, plen, len);
124 					has_star = 0;
125 					break;
126 				}
127 			}
128 
129 			// If we get here, then we never matched.
130 			if (has_star)
131 				goto nomatch;
132 
133 			continue;
134 		}
135 
136 		// If we have a question mark, we're allowed to match one-ahead.
137 		if (has_question) {
138 			int j;
139 
140 			for (j = 0; j <= has_question; j++) {
141 				if (strncmp(parts[i], can + j, MIN(plen, len-j)) == 0) {
142 					can += CLAMP(plen + j, plen, len);
143 					has_question = 0;
144 					break;
145 				}
146 			}
147 
148 			// If we get here, then we never matched.
149 			if (has_question)
150 				goto nomatch;
151 
152 			continue;
153 		}
154 
155 		// This is for normal matching.
156 		if (strncmp(parts[i], can, MIN(plen, len)) == 0) {
157 			can += MIN(plen, len);
158 			continue;
159 		}
160 
161 		// If we get here, then we never matched.
162 		goto nomatch;
163 	}
164 
165 	// Did we advance all the way to the end?
166 	// Or, did we have a star at the end?
167 	if (can[0] == '\0' || has_star || (has_question && can[1] == '\0'))
168 		goto match;
169 
170 nomatch:
171 	g_strfreev(parts);
172 	return NULL;
173 
174 match:
175 	g_strfreev(parts);
176 	return candidate;
177 
178 }
179 
180 // Returns a list of elements from the argument list that
181 // match the pattern. DOES make a copy of matching elements.
182 // The resulting GList should be freed with
183 //   g_list_destroy(glob_list);
match_glob_list(char * pattern,GList * list)184 GList *match_glob_list(char *pattern, GList *list)
185 {
186 	GList *match_list = NULL;
187 
188 	if (!list || !pattern)
189 		return NULL;
190 
191 	list = g_list_first(list);
192 	if (!list)
193 		return NULL;
194 
195 	while (list) {
196 		if (match_glob(pattern, (char *)list->data)) {
197 			match_list = g_list_prepend(match_list,
198 					g_strdup((char *)list->data));
199 		}
200 		if (! g_list_next(list)) break;
201 		list = g_list_next(list);
202 	}
203 
204 	// Return elements in the same relative order.
205 	if (match_list)
206 		match_list = g_list_reverse(match_list);
207 
208 	return match_list;
209 }
210 
211