]> diplodocus.org Git - nmh/blob - sbr/m_convert.c
Various IMAP protocol improvements
[nmh] / sbr / m_convert.c
1 /* m_convert.c -- parse a message range or sequence and set SELECTED
2 *
3 * This code is Copyright (c) 2002, by the authors of nmh. See the
4 * COPYRIGHT file in the root directory of the nmh distribution for
5 * complete copyright information.
6 */
7
8 #include <h/mh.h>
9 #include <h/utils.h>
10
11 /*
12 * error codes for sequence
13 * and message range processing
14 */
15 #define BADMSG (-2)
16 #define BADRNG (-3)
17 #define BADNEW (-4)
18 #define BADNUM (-5)
19 #define BADLST (-6)
20
21 #define FIRST 1
22 #define LAST 2
23
24
25 #define getnew(mp) (mp->hghmsg + 1)
26
27 static int convdir; /* convert direction */
28 static char *delimp;
29
30 /*
31 * static prototypes
32 */
33 static int m_conv (struct msgs *, char *, int);
34 static int attr (struct msgs *, char *);
35
36
37 int
38 m_convert (struct msgs *mp, char *name)
39 {
40 int first, last, found, count, err;
41 char *bp, *cp;
42
43 /* check if user defined sequence */
44 err = attr (mp, cp = name);
45
46 if (err == -1)
47 return 0;
48 if (err < 0)
49 goto badmsg;
50 if (err > 0)
51 return 1;
52 /*
53 * else err == 0, so continue
54 */
55
56 found = 0;
57
58 /*
59 * Check for special "new" sequence, which
60 * is valid only if ALLOW_NEW is set.
61 */
62 if ((mp->msgflags & ALLOW_NEW) && !strcmp (cp, "new")) {
63 if ((err = first = getnew (mp)) <= 0)
64 goto badmsg;
65 goto single;
66 }
67
68 if (!strcmp (cp, "all"))
69 cp = "first-last";
70
71 if ((err = first = m_conv (mp, cp, FIRST)) <= 0)
72 goto badmsg;
73
74 cp = delimp;
75 if (*cp != '\0' && *cp != '-' && *cp != ':' && *cp != '=') {
76 badelim:
77 inform("illegal argument delimiter: `%c'(0%o)", *delimp, *delimp);
78 return 0;
79 }
80
81 if (*cp == '-') {
82 cp++;
83 if ((err = last = m_conv (mp, cp, LAST)) <= 0) {
84 badmsg:
85 switch (err) {
86 case BADMSG:
87 inform("no %s message", cp);
88 break;
89
90 case BADNUM:
91 inform("message %s doesn't exist", cp);
92 break;
93
94 case BADRNG:
95 inform("message %s out of range 1-%d", cp, mp->hghmsg);
96 break;
97
98 case BADLST:
99 badlist:
100 inform("bad message list %s", name);
101 break;
102
103 case BADNEW:
104 inform("folder full, no %s message", name);
105 break;
106
107 default:
108 inform("no messages match specification");
109 }
110 return 0;
111 }
112
113 if (last < first)
114 goto badlist;
115 if (*delimp)
116 goto badelim;
117 if (first > mp->hghmsg || last < mp->lowmsg) {
118 rangerr:
119 inform("no messages in range %s", name);
120 return 0;
121 }
122
123 /* tighten the range to search */
124 if (last > mp->hghmsg)
125 last = mp->hghmsg;
126 if (first < mp->lowmsg)
127 first = mp->lowmsg;
128
129 } else if (*cp == ':' || *cp == '=') {
130
131 bool is_range = *cp == ':';
132 cp++;
133
134 if (*cp == '-') {
135 /* foo:-3 or foo=-3 */
136 convdir = -1;
137 cp++;
138 } else if (*cp == '+') {
139 /* foo:+3 or foo=+3 is same as foo:3 or foo=3 */
140 convdir = 1;
141 cp++;
142 }
143 if ((count = atoi (bp = cp)) == 0)
144 goto badlist;
145 while (isdigit ((unsigned char) *bp))
146 bp++;
147 if (*bp)
148 goto badelim;
149 if ((convdir > 0 && first > mp->hghmsg)
150 || (convdir < 0 && first < mp->lowmsg))
151 goto rangerr;
152
153 /* tighten the range to search */
154 if (first < mp->lowmsg)
155 first = mp->lowmsg;
156 if (first > mp->hghmsg)
157 first = mp->hghmsg;
158
159 for (last = first;
160 last >= mp->lowmsg && last <= mp->hghmsg;
161 last += convdir)
162 if (does_exist (mp, last))
163 if (--count <= 0)
164 break;
165 if (is_range) { /* a range includes any messages that exist */
166 if (last < mp->lowmsg)
167 last = mp->lowmsg;
168 if (last > mp->hghmsg)
169 last = mp->hghmsg;
170 if (last < first) {
171 count = last;
172 last = first;
173 first = count;
174 }
175 } else { /* looking for the nth message. if not enough, fail. */
176 if (last < mp->lowmsg || last > mp->hghmsg) {
177 inform("no such message");
178 return 0;
179 }
180 first = last;
181 }
182 } else {
183
184 single:
185 /*
186 * Single Message
187 *
188 * If ALLOW_NEW is set, then allow selecting of an
189 * empty slot. If ALLOW_NEW is not set, then we
190 * check if message is in-range and exists.
191 */
192 if (mp->msgflags & ALLOW_NEW) {
193 /*
194 * We can get into a case where the "cur" sequence is way out
195 * of range, and because it's allowed to not exist (think
196 * of "rmm; next") it doesn't get checked to make sure it's
197 * within the range of messages in seq_init(). So if our
198 * desired sequence is out of range of the allocated folder
199 * limits simply reallocate the folder so it's within range.
200 */
201 if (first < mp->lowoff || first > mp->hghoff)
202 mp = folder_realloc(mp, min(first, mp->lowoff),
203 max(first, mp->hghoff));
204
205 set_select_empty (mp, first);
206 } else {
207 if (first > mp->hghmsg
208 || first < mp->lowmsg
209 || !(does_exist (mp, first))) {
210 if (!strcmp (name, "cur") || !strcmp (name, "."))
211 inform("no %s message", name);
212 else
213 inform("message %d doesn't exist", first);
214 return 0;
215 }
216 }
217 last = first; /* range of 1 */
218 }
219
220 /*
221 * Cycle through the range and select the messages
222 * that exist. If ALLOW_NEW is set, then we also check
223 * if we are selecting an empty slot.
224 */
225 for (; first <= last; first++) {
226 if (does_exist (mp, first) ||
227 ((mp->msgflags & ALLOW_NEW) && is_select_empty (mp, first))) {
228 if (!is_selected (mp, first)) {
229 set_selected (mp, first);
230 mp->numsel++;
231 if (mp->lowsel == 0 || first < mp->lowsel)
232 mp->lowsel = first;
233 if (first > mp->hghsel)
234 mp->hghsel = first;
235 }
236 found++;
237 }
238 }
239
240 if (!found)
241 goto rangerr;
242
243 return 1;
244 }
245
246 /*
247 * Convert the various message names to
248 * their numeric values.
249 *
250 * n (integer)
251 * prev
252 * next
253 * first
254 * last
255 * cur
256 * . (same as cur)
257 */
258
259 static int
260 m_conv (struct msgs *mp, char *str, int call)
261 {
262 int i;
263 char *cp, *bp;
264 char buf[16];
265
266 convdir = 1;
267 cp = bp = str;
268 if (isdigit ((unsigned char) *cp)) {
269 while (isdigit ((unsigned char) *bp))
270 bp++;
271 delimp = bp;
272 i = atoi (cp);
273
274 if (i <= mp->hghmsg)
275 return i;
276 if (*delimp || call == LAST)
277 return mp->hghmsg + 1;
278 if (mp->msgflags & ALLOW_NEW)
279 return BADRNG;
280 return BADNUM;
281 }
282
283 /* doesn't enforce lower case */
284 for (bp = buf; (isalpha((unsigned char) *cp) || *cp == '.')
285 && (bp - buf < (int) sizeof(buf) - 1); )
286 {
287 *bp++ = *cp++;
288 }
289 *bp++ = '\0';
290 delimp = cp;
291
292 if (!strcmp (buf, "first"))
293 return mp->hghmsg || !(mp->msgflags & ALLOW_NEW) ?
294 mp->lowmsg : BADMSG;
295
296 if (!strcmp (buf, "last")) {
297 convdir = -1;
298 return mp->hghmsg || !(mp->msgflags & ALLOW_NEW) ? mp->hghmsg : BADMSG;
299 }
300
301 if (!strcmp (buf, "cur") || !strcmp (buf, "."))
302 return mp->curmsg > 0 ? mp->curmsg : BADMSG;
303
304 if (!strcmp (buf, "prev")) {
305 convdir = -1;
306 for (i = (mp->curmsg <= mp->hghmsg) ? mp->curmsg - 1 : mp->hghmsg;
307 i >= mp->lowmsg; i--) {
308 if (does_exist (mp, i))
309 return i;
310 }
311 return BADMSG;
312 }
313
314 if (!strcmp (buf, "next")) {
315 for (i = (mp->curmsg >= mp->lowmsg) ? mp->curmsg + 1 : mp->lowmsg;
316 i <= mp->hghmsg; i++) {
317 if (does_exist (mp, i))
318 return i;
319 }
320 return BADMSG;
321 }
322
323 return BADLST;
324 }
325
326 /*
327 * Handle user defined sequences.
328 * They can take the following forms:
329 *
330 * seq
331 * seq:prev
332 * seq:next
333 * seq:first
334 * seq:last
335 * seq:+n
336 * seq:-n
337 * seq:n
338 */
339
340 static int
341 attr (struct msgs *mp, char *cp)
342 {
343 char *dp;
344 char *bp = NULL;
345 char *ep;
346 char op;
347 int i, j;
348 int found,
349 count = 0, /* range given? else use entire sequence */
350 first = 0,
351 start = 0;
352
353 /* hack for "cur-name", "cur-n", etc. */
354 if (!strcmp (cp, "cur"))
355 return 0;
356 if (has_prefix(cp, "cur")) {
357 if (cp[3] == ':' || cp[3] == '=')
358 return 0;
359 }
360
361 /* Check for sequence negation */
362 bool inverted = false;
363 if ((dp = context_find (nsequence)) && *dp != '\0' && ssequal (dp, cp)) {
364 inverted = true;
365 cp += strlen (dp);
366 }
367
368 convdir = 1; /* convert direction */
369
370 for (dp = cp; isalnum((unsigned char)*dp); dp++)
371 continue;
372
373 bool just_one = *dp == '='; /* want entire sequence or range */
374
375 if (*dp == ':') {
376 bp = dp++;
377 count = 1;
378
379 /*
380 * seq:prev (or)
381 * seq:next (or)
382 * seq:first (or)
383 * seq:last
384 */
385 if (isalpha ((unsigned char) *dp)) {
386 if (!strcmp (dp, "prev")) {
387 convdir = -1;
388 first = (mp->curmsg > 0) && (mp->curmsg <= mp->hghmsg)
389 ? mp->curmsg - 1
390 : mp->hghmsg;
391 start = first;
392 }
393 else if (!strcmp (dp, "next")) {
394 convdir = 1;
395 first = (mp->curmsg >= mp->lowmsg)
396 ? mp->curmsg + 1
397 : mp->lowmsg;
398 start = first;
399 }
400 else if (!strcmp (dp, "first")) {
401 convdir = 1;
402 start = mp->lowmsg;
403 }
404 else if (!strcmp (dp, "last")) {
405 convdir = -1;
406 start = mp->hghmsg;
407 }
408 else {
409 return BADLST;
410 }
411 } else {
412 /*
413 * seq:n (or)
414 * seq:+n (or)
415 * seq:-n
416 */
417 if (*dp == '+') {
418 /* foo:+3 is same as foo:3 */
419 dp++;
420 convdir = 1;
421 start = mp->lowmsg;
422 } else if (*dp == '-' || *dp == ':') {
423 /* foo:-3 or foo::3 */
424 dp++;
425 convdir = -1;
426 start = mp->hghmsg;
427 }
428 count = strtol(dp,&ep,10);
429 if (count == 0 || *ep) /* 0 illegal */
430 return BADLST;
431 }
432
433 op = *bp;
434 *bp = '\0'; /* temporarily terminate sequence name */
435 } else if (*dp == '=') {
436
437 bp = dp++;
438
439 if (*dp == '+') {
440 /* foo=+3 is same as foo=3 */
441 dp++;
442 convdir = 1;
443 start = mp->lowmsg;
444 } else if (*dp == '-') {
445 /* foo=-3 */
446 dp++;
447 convdir = -1;
448 start = mp->hghmsg;
449 }
450
451 count = strtol(dp,&ep,10); /* 0 illegal */
452 if (count == 0 || *ep)
453 return BADLST;
454
455 op = *bp;
456 *bp = '\0'; /* temporarily terminate sequence name */
457 }
458
459 i = seq_getnum (mp, cp); /* get index of sequence */
460
461 if (bp)
462 *bp = op; /* restore sequence name */
463 if (i == -1)
464 return 0;
465
466 found = 0; /* count the number we select for this argument */
467
468 if (!start)
469 start = mp->lowmsg;
470
471 for (j = start; j >= mp->lowmsg && j <= mp->hghmsg; j += convdir) {
472
473 if (does_exist (mp, j)
474 && inverted ? !in_sequence (mp, i, j) : in_sequence (mp, i, j)) {
475 found++;
476 /* we want all that we find, or just the last in the +/_ case */
477 if (!just_one || found >= count) {
478 if (!is_selected (mp, j)) {
479 set_selected (mp, j);
480 mp->numsel++;
481 if (mp->lowsel == 0 || j < mp->lowsel)
482 mp->lowsel = j;
483 if (j > mp->hghsel)
484 mp->hghsel = j;
485 }
486 }
487 /*
488 * If we have any sort of limit, then break out
489 * once we've found enough.
490 */
491 if (count && found >= count)
492 break;
493
494 }
495 }
496
497 if (mp->numsel > 0)
498 return mp->numsel;
499
500 if (first || just_one)
501 return BADMSG;
502 inform("sequence %s %s", cp, inverted ? "full" : "empty");
503 return -1;
504 }