xref: /plan9/sys/src/cmd/ip/imap4d/list.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include "imap4d.h"
6 
7 #define SUBSCRIBED	"imap.subscribed"
8 
9 static int	matches(char *ref, char *pat, char *name);
10 static int	mayMatch(char *pat, char *name, int star);
11 static int	checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir);
12 static int	listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime);
13 static int	listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm);
14 static int	mkSubscribed(void);
15 
16 static long
17 listMtime(char *file)
18 {
19 	Dir *d;
20 	long mtime;
21 
22 	d = cdDirstat(mboxDir, file);
23 	if(d == nil)
24 		return 0;
25 	mtime = d->mtime;
26 	free(d);
27 	return mtime;
28 }
29 
30 /*
31  * check for subscribed mailboxes
32  * each line is either a comment starting with #
33  * or is a subscribed mailbox name
34  */
35 int
36 lsubBoxes(char *cmd, char *ref, char *pat)
37 {
38 	MbLock *mb;
39 	Dir *d;
40 	Biobuf bin;
41 	char *s;
42 	long mtime;
43 	int fd, ok, isdir;
44 
45 	mb = mbLock();
46 	if(mb == nil)
47 		return 0;
48 	fd = cdOpen(mboxDir, SUBSCRIBED, OREAD);
49 	if(fd < 0)
50 		fd = mkSubscribed();
51 	if(fd < 0){
52 		mbUnlock(mb);
53 		return 0;
54 	}
55 	ok = 0;
56 	Binit(&bin, fd, OREAD);
57 	while(s = Brdline(&bin, '\n')){
58 		s[Blinelen(&bin) - 1] = '\0';
59 		if(s[0] == '#')
60 			continue;
61 		isdir = 1;
62 		if(cistrcmp(s, "INBOX") == 0){
63 			mtime = listMtime("mbox");
64 			isdir = 0;
65 		}else{
66 			d = cdDirstat(mboxDir, s);
67 			if(d != nil){
68 				mtime = d->mtime;
69 				if(!(d->mode & DMDIR))
70 					isdir = 0;
71 				free(d);
72 			}else
73 				mtime = 0;
74 		}
75 		ok |= checkMatch(cmd, ref, pat, s, mtime, isdir);
76 	}
77 	Bterm(&bin);
78 	close(fd);
79 	mbUnlock(mb);
80 	return ok;
81 }
82 
83 static int
84 mkSubscribed(void)
85 {
86 	int fd;
87 
88 	fd = cdCreate(mboxDir, SUBSCRIBED, ORDWR, 0664);
89 	if(fd < 0)
90 		return -1;
91 	fprint(fd, "#imap4 subscription list\nINBOX\n");
92 	seek(fd, 0, 0);
93 	return fd;
94 }
95 
96 /*
97  * either subscribe or unsubscribe to a mailbox
98  */
99 int
100 subscribe(char *mbox, int how)
101 {
102 	MbLock *mb;
103 	char *s, *in, *ein;
104 	int fd, tfd, ok, nmbox;
105 
106 	if(cistrcmp(mbox, "inbox") == 0)
107 		mbox = "INBOX";
108 	mb = mbLock();
109 	if(mb == nil)
110 		return 0;
111 	fd = cdOpen(mboxDir, SUBSCRIBED, ORDWR);
112 	if(fd < 0)
113 		fd = mkSubscribed();
114 	if(fd < 0){
115 		mbUnlock(mb);
116 		return 0;
117 	}
118 	in = readFile(fd);
119 	if(in == nil){
120 		mbUnlock(mb);
121 		return 0;
122 	}
123 	nmbox = strlen(mbox);
124 	s = strstr(in, mbox);
125 	while(s != nil && (s != in && s[-1] != '\n' || s[nmbox] != '\n'))
126 		s = strstr(s+1, mbox);
127 	ok = 0;
128 	if(how == 's' && s == nil){
129 		if(fprint(fd, "%s\n", mbox) > 0)
130 			ok = 1;
131 	}else if(how == 'u' && s != nil){
132 		ein = strchr(s, '\0');
133 		memmove(s, &s[nmbox+1], ein - &s[nmbox+1]);
134 		ein -= nmbox+1;
135 		tfd = cdOpen(mboxDir, SUBSCRIBED, OWRITE|OTRUNC);
136 		if(tfd >= 0 && seek(fd, 0, 0) >= 0 && write(fd, in, ein-in) == ein-in)
137 			ok = 1;
138 		if(tfd > 0)
139 			close(tfd);
140 	}else
141 		ok = 1;
142 	close(fd);
143 	mbUnlock(mb);
144 	return ok;
145 }
146 
147 /*
148  * stupidly complicated so that % doesn't read entire directory structure
149  * yet * works
150  * note: in most places, inbox is case-insensitive,
151  * but here INBOX is checked for a case-sensitve match.
152  */
153 int
154 listBoxes(char *cmd, char *ref, char *pat)
155 {
156 	int ok;
157 
158 	ok = checkMatch(cmd, ref, pat, "INBOX", listMtime("mbox"), 0);
159 	return ok | listMatch(cmd, ref, pat, ref, pat);
160 }
161 
162 /*
163  * look for all messages which may match the pattern
164  * punt when a * is reached
165  */
166 static int
167 listMatch(char *cmd, char *ref, char *pat, char *mbox, char *mm)
168 {
169 	Dir *dir, *dirs;
170 	char *mdir, *m, *mb, *wc;
171 	long mode;
172 	int c, i, nmb, nmdir, nd, ok, fd;
173 
174 	mdir = nil;
175 	for(m = mm; c = *m; m++){
176 		if(c == '%' || c == '*'){
177 			if(mdir == nil){
178 				fd = cdOpen(mboxDir, ".", OREAD);
179 				if(fd < 0)
180 					return 0;
181 				mbox = "";
182 				nmdir = 0;
183 			}else{
184 				*mdir = '\0';
185 				fd = cdOpen(mboxDir, mbox, OREAD);
186 				*mdir = '/';
187 				nmdir = mdir - mbox + 1;
188 				if(fd < 0)
189 					return 0;
190 				dir = dirfstat(fd);
191 				if(dir == nil){
192 					close(fd);
193 					return 0;
194 				}
195 				mode = dir->mode;
196 				free(dir);
197 				if(!(mode & DMDIR))
198 					break;
199 			}
200 			wc = m;
201 			for(; c = *m; m++)
202 				if(c == '/')
203 					break;
204 			nmb = nmdir + strlen(m) + MboxNameLen + 3;
205 			mb = emalloc(nmb);
206 			strncpy(mb, mbox, nmdir);
207 			ok = 0;
208 			while((nd = dirread(fd, &dirs)) > 0){
209 				for(i = 0; i < nd; i++){
210 					if(*wc == '*' && (dirs[i].mode & DMDIR) && mayMatch(mm, dirs[i].name, 1)){
211 						snprint(mb+nmdir, nmb-nmdir, "%s", dirs[i].name);
212 						ok |= listAll(cmd, ref, pat, mb, dirs[i].mtime);
213 					}else if(mayMatch(mm, dirs[i].name, 0)){
214 						snprint(mb+nmdir, nmb-nmdir, "%s%s", dirs[i].name, m);
215 						if(*m == '\0')
216 							ok |= checkMatch(cmd, ref, pat, mb, dirs[i].mtime, (dirs[i].mode & DMDIR) == DMDIR);
217 						else if(dirs[i].mode & DMDIR)
218 							ok |= listMatch(cmd, ref, pat, mb, mb + nmdir + strlen(dirs[i].name));
219 					}
220 				}
221 				free(dirs);
222 			}
223 			close(fd);
224 			free(mb);
225 			return ok;
226 		}
227 		if(c == '/'){
228 			mdir = m;
229 			mm = m + 1;
230 		}
231 	}
232 	m = mbox;
233 	if(*mbox == '\0')
234 		m = ".";
235 	dir = cdDirstat(mboxDir, m);
236 	if(dir == nil)
237 		return 0;
238 	ok = checkMatch(cmd, ref, pat, mbox, dir->mtime, (dir->mode & DMDIR) == DMDIR);
239 	free(dir);
240 	return ok;
241 }
242 
243 /*
244  * too hard: recursively list all files rooted at mbox,
245  * and list checkMatch figure it out
246  */
247 static int
248 listAll(char *cmd, char *ref, char *pat, char *mbox, long mtime)
249 {
250 	Dir *dirs;
251 	char *mb;
252 	int i, nmb, nd, ok, fd;
253 
254 	ok = checkMatch(cmd, ref, pat, mbox, mtime, 1);
255 	fd = cdOpen(mboxDir, mbox, OREAD);
256 	if(fd < 0)
257 		return ok;
258 
259 	nmb = strlen(mbox) + MboxNameLen + 2;
260 	mb = emalloc(nmb);
261 	while((nd = dirread(fd, &dirs)) > 0){
262 		for(i = 0; i < nd; i++){
263 			snprint(mb, nmb, "%s/%s", mbox, dirs[i].name);
264 			if(dirs[i].mode & DMDIR)
265 				ok |= listAll(cmd, ref, pat, mb, dirs[i].mtime);
266 			else
267 				ok |= checkMatch(cmd, ref, pat, mb, dirs[i].mtime, 0);
268 		}
269 		free(dirs);
270 	}
271 	close(fd);
272 	free(mb);
273 	return ok;
274 }
275 
276 static int
277 mayMatch(char *pat, char *name, int star)
278 {
279 	Rune r;
280 	int i, n;
281 
282 	for(; *pat && *pat != '/'; pat += n){
283 		r = *(uchar*)pat;
284 		if(r < Runeself)
285 			n = 1;
286 		else
287 			n = chartorune(&r, pat);
288 
289 		if(r == '*' || r == '%'){
290 			pat += n;
291 			if(r == '*' && star || *pat == '\0' || *pat == '/')
292 				return 1;
293 			while(*name){
294 				if(mayMatch(pat, name, star))
295 					return 1;
296 				name += chartorune(&r, name);
297 			}
298 			return 0;
299 		}
300 		for(i = 0; i < n; i++)
301 			if(name[i] != pat[i])
302 				return 0;
303 		name += n;
304 	}
305 	if(*name == '\0')
306 		return 1;
307 	return 0;
308 }
309 
310 /*
311  * mbox is a mailbox name which might match pat.
312  * verify the match
313  * generates response
314  */
315 static int
316 checkMatch(char *cmd, char *ref, char *pat, char *mbox, long mtime, int isdir)
317 {
318 	char *s, *flags;
319 
320 	if(!matches(ref, pat, mbox) || !okMbox(mbox))
321 		return 0;
322 	if(strcmp(mbox, ".") == 0)
323 		mbox = "";
324 
325 	if(isdir)
326 		flags = "(\\Noselect)";
327 	else{
328 		s = impName(mbox);
329 		if(s != nil && listMtime(s) < mtime)
330 			flags = "(\\Noinferiors \\Marked)";
331 		else
332 			flags = "(\\Noinferiors)";
333 	}
334 
335 	s = strmutf7(mbox);
336 	if(s != nil)
337 		Bprint(&bout, "* %s %s \"/\" %s\r\n", cmd, flags, s);
338 	return 1;
339 }
340 
341 static int
342 matches(char *ref, char *pat, char *name)
343 {
344 	Rune r;
345 	int i, n;
346 
347 	while(ref != pat)
348 		if(*name++ != *ref++)
349 			return 0;
350 	for(; *pat; pat += n){
351 		r = *(uchar*)pat;
352 		if(r < Runeself)
353 			n = 1;
354 		else
355 			n = chartorune(&r, pat);
356 
357 		if(r == '*'){
358 			pat += n;
359 			if(*pat == '\0')
360 				return 1;
361 			while(*name){
362 				if(matches(pat, pat, name))
363 					return 1;
364 				name += chartorune(&r, name);
365 			}
366 			return 0;
367 		}
368 		if(r == '%'){
369 			pat += n;
370 			while(*name && *name != '/'){
371 				if(matches(pat, pat, name))
372 					return 1;
373 				name += chartorune(&r, name);
374 			}
375 			pat -= n;
376 			continue;
377 		}
378 		for(i = 0; i < n; i++)
379 			if(name[i] != pat[i])
380 				return 0;
381 		name += n;
382 	}
383 	if(*name == '\0')
384 		return 1;
385 	return 0;
386 }
387