1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 1999-2021 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 /* MH message sets. */
18
19 #include <mh.h>
20 #include <mailutils/sys/msgset.h>
21
22 size_t
mh_msgset_first(mu_msgset_t msgset,int uid)23 mh_msgset_first (mu_msgset_t msgset, int uid)
24 {
25 size_t n;
26 int rc = mu_msgset_first (msgset, &n);
27 if (rc)
28 {
29 mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_first", NULL, rc);
30 exit (1);
31 }
32 if (uid)
33 {
34 rc = mu_mailbox_translate (msgset->mbox, MU_MAILBOX_MSGNO_TO_UID, n, &n);
35 if (rc)
36 {
37 mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_translate", NULL, rc);
38 exit (1);
39 }
40 }
41 return n;
42 }
43
44 size_t
mh_msgset_last(mu_msgset_t msgset,int uid)45 mh_msgset_last (mu_msgset_t msgset, int uid)
46 {
47 size_t n;
48 int rc = mu_msgset_last (msgset, &n);
49 if (rc)
50 {
51 mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_last", NULL, rc);
52 exit (1);
53 }
54 if (uid)
55 {
56 rc = mu_mailbox_translate (msgset->mbox, MU_MAILBOX_MSGNO_TO_UID, n, &n);
57 if (rc)
58 {
59 mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_translate", NULL, rc);
60 exit (1);
61 }
62 }
63 return n;
64 }
65
66 int
mh_msgset_single_message(mu_msgset_t msgset)67 mh_msgset_single_message (mu_msgset_t msgset)
68 {
69 int rc;
70 mu_list_t list;
71 struct mu_msgrange *r;
72 size_t count;
73
74 rc = mu_msgset_get_list (msgset, &list);
75 if (rc)
76 {
77 mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_get_list", NULL, rc);
78 exit (1);
79 }
80 rc = mu_list_count (list, &count);
81 if (rc)
82 {
83 mu_diag_funcall (MU_DIAG_ERROR, "mu_list_count", NULL, rc);
84 exit (1);
85 }
86 if (count != 1)
87 return 0;
88 rc = mu_list_head (list, (void**)&r);
89 if (rc)
90 {
91 mu_diag_funcall (MU_DIAG_ERROR, "mu_list_get", NULL, rc);
92 exit (1);
93 }
94 return r->msg_beg == r->msg_end;
95 }
96
97 struct msgset_parser
98 {
99 mu_msgset_t msgset;
100 char *curp;
101 int argc;
102 char **argv;
103
104 int sign;
105 size_t number;
106 int validuid;
107 };
108
109 static void
msgset_parser_init(struct msgset_parser * parser,mu_mailbox_t mbox,int argc,char ** argv)110 msgset_parser_init (struct msgset_parser *parser, mu_mailbox_t mbox,
111 int argc, char **argv)
112 {
113 int rc;
114
115 rc = mu_msgset_create (&parser->msgset, mbox, MU_MSGSET_NUM);//FIXME: flags?
116 if (rc)
117 {
118 mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_create", NULL, rc);
119 exit (1);
120 }
121
122 parser->argc = argc;
123 parser->argv = argv;
124 parser->curp = "";
125
126 parser->sign = 0;
127 parser->number = 0;
128 }
129
130 static void
msgset_abort(const char * arg)131 msgset_abort (const char *arg)
132 {
133 mu_error (_("bad message list `%s'"), arg);
134 exit (1);
135 }
136
137 static void
emptyrange_abort(const char * range)138 emptyrange_abort (const char *range)
139 {
140 mu_error (_("no messages in range %s"), range);
141 exit (1);
142 }
143
144 /* Advance parser to the next argument */
145 static int
nextarg(struct msgset_parser * parser)146 nextarg (struct msgset_parser *parser)
147 {
148 if (parser->argc == 0)
149 return 0;
150 parser->argc--;
151 parser->curp = *parser->argv++;
152 return 1;
153 }
154
155 static void msgset_parser_run (struct msgset_parser *parser);
156
157 static int
_expand_sequence(struct msgset_parser * parser,char * term)158 _expand_sequence (struct msgset_parser *parser, char *term)
159 {
160 struct mu_wordsplit ws;
161 const char *listp;
162 int negate = 0;
163
164 listp = mh_global_sequences_get (parser->msgset->mbox, term, NULL);
165 if (!listp)
166 {
167 int len;
168 const char *neg = mh_global_profile_get ("Sequence-Negation", NULL);
169 if (!neg)
170 return 1;
171 len = strlen (neg);
172 if (strncmp (term, neg, len))
173 return 1;
174 negate = 1;
175 listp = mh_global_sequences_get (parser->msgset->mbox, term + len, NULL);
176 if (!listp)
177 return 1;
178 }
179
180 if (mu_wordsplit (listp, &ws, MU_WRDSF_DEFFLAGS))
181 {
182 mu_error (_("cannot split line `%s': %s"), listp,
183 mu_wordsplit_strerror (&ws));
184 exit (1);
185 }
186 else
187 {
188 int rc;
189 struct msgset_parser clone;
190
191 msgset_parser_init (&clone, parser->msgset->mbox,
192 ws.ws_wordc, ws.ws_wordv);
193 msgset_parser_run (&clone);
194 mu_wordsplit_free (&ws);
195 if (negate)
196 {
197 mu_msgset_t negset;
198
199 rc = mu_msgset_negate (clone.msgset, &negset);
200 if (rc)
201 {
202 mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_negate", NULL, rc);
203 exit (1);
204 }
205 mu_msgset_free (clone.msgset);
206 clone.msgset = negset;
207 }
208 rc = mu_msgset_add (parser->msgset, clone.msgset);
209 if (rc)
210 {
211 mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_add", NULL, rc);
212 exit (1);
213 }
214 mu_msgset_free (clone.msgset);
215 }
216
217 return 0;
218 }
219
220 static int
parse_count(struct msgset_parser * parser)221 parse_count (struct msgset_parser *parser)
222 {
223 char *endp;
224 if (!*parser->curp && nextarg (parser) == 0)
225 return 0;
226 if (*parser->curp == '-')
227 {
228 parser->sign = 1;
229 parser->curp++;
230 }
231 else if (*parser->curp == '+')
232 {
233 parser->sign = 0;
234 parser->curp++;
235 }
236 parser->number = strtoul (parser->curp, &endp, 10);
237 if (*endp)
238 msgset_abort (parser->curp);
239 parser->curp = endp;
240 return 1;
241 }
242
243
244 static int
msgset_first(mu_mailbox_t mbox,size_t * pnum)245 msgset_first (mu_mailbox_t mbox, size_t *pnum)
246 {
247 *pnum = 1;
248 return 0;
249 }
250
251 static int
msgset_last(mu_mailbox_t mbox,size_t * pnum)252 msgset_last (mu_mailbox_t mbox, size_t *pnum)
253 {
254 int rc;
255
256 rc = mu_mailbox_messages_count (mbox, pnum);
257 if (rc)
258 {
259 mu_error (_("cannot get last message: %s"), mu_strerror (rc));
260 exit (1);
261 }
262 return 0;
263 }
264
265 static int
msgset_cur(mu_mailbox_t mbox,size_t * pnum)266 msgset_cur (mu_mailbox_t mbox, size_t *pnum)
267 {
268 size_t num;
269 mh_mailbox_get_cur (mbox, &num);
270 return mu_mailbox_translate (mbox, MU_MAILBOX_UID_TO_MSGNO, num, pnum);
271 }
272
273 static int
msgset_prev(mu_mailbox_t mbox,size_t * pnum)274 msgset_prev (mu_mailbox_t mbox, size_t *pnum)
275 {
276 size_t cur_n = 0;
277 msgset_cur (mbox, &cur_n);
278 if (cur_n < 1)
279 {
280 mu_error (_("no prev message"));
281 exit (1);
282 }
283 *pnum = cur_n - 1;
284 return 0;
285 }
286
287 static int
msgset_next(mu_mailbox_t mbox,size_t * pnum)288 msgset_next (mu_mailbox_t mbox, size_t *pnum)
289 {
290 size_t cur_n = 0, total = 0;
291 msgset_cur (mbox, &cur_n);
292 mu_mailbox_messages_count (mbox, &total);
293 if (cur_n + 1 > total)
294 {
295 mu_error (_("no next message"));
296 exit (1);
297 }
298 *pnum = cur_n + 1;
299 return 0;
300 }
301
302 struct msgset_keyword
303 {
304 char *name;
305 size_t len;
306 int (*handler) (mu_mailbox_t mbox, size_t *pnum);
307 int sign;
308 };
309
310 static struct msgset_keyword keywords[] = {
311 #define S(s) #s, sizeof (#s) - 1
312 { S(first), msgset_first, 0 },
313 { S(last), msgset_last, 1 },
314 { S(prev), msgset_prev, 1 },
315 { S(next), msgset_next, 0 },
316 { S(cur), msgset_cur, 0 },
317 { NULL }
318 };
319
320 #define PARSE_EOF 0
321 #define PARSE_MORE 1
322 #define PARSE_SUCCESS 2
323
324 /* term : NUMBER
325 | "first"
326 | "last"
327 | "cur"
328 | "prev"
329 | "next"
330 ;
331 */
332 static int
parse_term(struct msgset_parser * parser,int seq)333 parse_term (struct msgset_parser *parser, int seq)
334 {
335 size_t tlen;
336 char *term;
337
338 if (!*parser->curp && nextarg (parser) == 0)
339 return PARSE_EOF;
340
341 term = parser->curp;
342 parser->curp = mu_str_skip_class (term, MU_CTYPE_ALPHA|MU_CTYPE_DIGIT);
343 tlen = parser->curp - term;
344 if (mu_isalpha (*term))
345 {
346 struct msgset_keyword *p;
347
348 for (p = keywords; p->name; p++)
349 if (tlen == p->len && memcmp (p->name, term, tlen) == 0)
350 {
351 size_t num;
352
353 if (p->handler (parser->msgset->mbox, &num))
354 msgset_abort (term);
355 parser->number = num;
356 parser->sign = p->sign;
357 parser->validuid = 1;
358 return PARSE_MORE;
359 }
360
361 if (*parser->curp == 0 && seq)
362 {
363 /* See if it is a user-defined sequence */
364 if (_expand_sequence (parser, term) == 0)
365 return PARSE_SUCCESS;
366 }
367 msgset_abort (term);
368 }
369 else if (mu_isdigit (*term))
370 {
371 char *endp;
372 size_t num = strtoul (term, &endp, 10);
373 if (endp != parser->curp)
374 msgset_abort (term);
375
376 if (mu_mailbox_translate (parser->msgset->mbox,
377 MU_MAILBOX_UID_TO_MSGNO,
378 num, &parser->number))
379 {
380 parser->validuid = 0;
381 parser->number = num;
382 }
383 else
384 parser->validuid = 1;
385 parser->sign = 0;
386 }
387 else
388 msgset_abort (term);
389 return PARSE_MORE;
390 }
391
392 static void
add_messages(struct msgset_parser * parser,size_t start,size_t count,int sign)393 add_messages (struct msgset_parser *parser, size_t start, size_t count,
394 int sign)
395 {
396 int rc;
397
398 if (start == 0)
399 start = 1;
400 if (sign)
401 {
402 if (count > start)
403 count = start;
404 rc = mu_msgset_add_range (parser->msgset, start, start - count + 1,
405 MU_MSGSET_NUM);
406 }
407 else
408 {
409 size_t total;
410
411 mu_mailbox_messages_count (parser->msgset->mbox, &total);
412 if (start + count > total)
413 {
414 count = total - start + 1;
415 if (count == 0)
416 emptyrange_abort (parser->argv[-1]);
417 }
418 rc = mu_msgset_add_range (parser->msgset, start, start + count - 1,
419 MU_MSGSET_NUM);
420 }
421
422 if (rc)
423 {
424 mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_add_range", NULL, rc);
425 exit (1);
426 }
427 }
428
429 /* range: term '-' term
430 | term ':' count
431 ;
432 count: NUMBER
433 | '+' NUMBER
434 | '-' NUMBER
435 ;
436 */
437 static int
parse_range(struct msgset_parser * parser)438 parse_range (struct msgset_parser *parser)
439 {
440 size_t start;
441
442 switch (parse_term (parser, 1))
443 {
444 case PARSE_EOF:
445 return 0;
446
447 case PARSE_SUCCESS:
448 return 1;
449
450 case PARSE_MORE:
451 break;
452 }
453
454 start = parser->number;
455
456 if (*parser->curp == ':')
457 {
458 int validuid = parser->validuid;
459 parser->curp++;
460 if (parse_count (parser) == 0)
461 return 0;
462 if (!validuid)
463 {
464 if (parser->sign)
465 {
466 while (1)
467 {
468 if (--start == 0)
469 emptyrange_abort (parser->argv[-1]);
470 if (mu_mailbox_translate (parser->msgset->mbox,
471 MU_MAILBOX_UID_TO_MSGNO,
472 start, &start) == 0)
473 break;
474 }
475 }
476 else
477 {
478 size_t total, lastuid;
479
480 msgset_last (parser->msgset->mbox, &total);
481 mu_mailbox_translate (parser->msgset->mbox,
482 MU_MAILBOX_MSGNO_TO_UID,
483 total, &lastuid);
484 if (start > lastuid)
485 emptyrange_abort (parser->argv[-1]);
486 else
487 while (1)
488 {
489 if (start == lastuid)
490 {
491 start = total;
492 break;
493 }
494 ++start;
495 if (mu_mailbox_translate (parser->msgset->mbox,
496 MU_MAILBOX_UID_TO_MSGNO,
497 start, &start) == 0)
498 break;
499 }
500 }
501 }
502 add_messages (parser, start, parser->number, parser->sign);
503 return 1;
504 }
505 else if (*parser->curp == '-')
506 {
507 int validuid = parser->validuid;
508 int rc;
509
510 parser->curp++;
511 if (parse_term (parser, 0) == PARSE_EOF)
512 return 0;
513
514 /* If any of UIDs does not exist, try to find the nearest
515 existing one: */
516 if (!validuid || !parser->validuid)
517 {
518 size_t lastuid, num, maxnum;
519
520 /* Order the limits */
521 if (!parser->validuid)
522 maxnum = parser->number;
523 else
524 mu_mailbox_translate (parser->msgset->mbox,
525 MU_MAILBOX_MSGNO_TO_UID,
526 parser->number, &maxnum);
527 if (!validuid)
528 num = start;
529 else
530 mu_mailbox_translate (parser->msgset->mbox,
531 MU_MAILBOX_MSGNO_TO_UID,
532 start, &num);
533
534 if (num > maxnum)
535 {
536 int v;
537 size_t n;
538
539 n = parser->number;
540 v = parser->validuid;
541 parser->number = start;
542 parser->validuid = validuid;
543 start = n;
544 validuid = v;
545 }
546
547 /* Adjust upper bound */
548 msgset_last (parser->msgset->mbox, &num);
549 mu_mailbox_translate (parser->msgset->mbox, MU_MAILBOX_MSGNO_TO_UID,
550 num, &lastuid);
551
552 if (!parser->validuid && parser->number > lastuid)
553 {
554 parser->number = num;
555 parser->validuid = 1;
556 }
557
558 /* Shift the bounds towards each other until they resolve to
559 existing UIDs or clash */
560 do
561 {
562 if (!validuid)
563 {
564 ++start;
565 if (start > lastuid)
566 emptyrange_abort (parser->argv[-1]);
567 rc = mu_mailbox_translate (parser->msgset->mbox,
568 MU_MAILBOX_UID_TO_MSGNO,
569 start, &start);
570 if (rc == 0)
571 validuid = 1;
572 }
573 if (!parser->validuid)
574 {
575 if (parser->number == 1)
576 emptyrange_abort (parser->argv[-1]);
577 --parser->number;
578 rc = mu_mailbox_translate (parser->msgset->mbox,
579 MU_MAILBOX_UID_TO_MSGNO,
580 parser->number, &num);
581 if (rc == 0)
582 {
583 lastuid = parser->number;
584 parser->number = num;
585 parser->validuid = 1;
586 }
587 }
588 }
589 while (!validuid || !parser->validuid);
590 }
591 mu_msgset_add_range (parser->msgset, start, parser->number,
592 MU_MSGSET_NUM);
593 }
594 else if (!parser->validuid)
595 {
596 mu_error (_("message %s does not exist"), parser->argv[-1]);
597 exit (1);
598 }
599 else
600 mu_msgset_add_range (parser->msgset, start, start, MU_MSGSET_NUM);
601 return 1;
602 }
603
604
605 /* Parse a message specification saved in a configured PARSER. */
606 static void
msgset_parser_run(struct msgset_parser * parser)607 msgset_parser_run (struct msgset_parser *parser)
608 {
609 while (parse_range (parser))
610 ;
611 }
612
613 /* Parse a message specification from (argc;argv). */
614 void
mh_msgset_parse(mu_msgset_t * msgset,mu_mailbox_t mbox,int argc,char ** argv,const char * def)615 mh_msgset_parse (mu_msgset_t *msgset, mu_mailbox_t mbox,
616 int argc, char **argv, const char *def)
617 {
618 struct msgset_parser parser;
619 char *xargv[2];
620
621 if (argc == 0)
622 {
623 argc = 1;
624 argv = xargv;
625 argv[0] = (char*) (def ? def : "cur");
626 argv[1] = NULL;
627 }
628
629 if (argc == 1 &&
630 (strcmp (argv[0], "all") == 0 || strcmp (argv[0], ".") == 0))
631 {
632 argc = 1;
633 argv = xargv;
634 argv[0] = "first-last";
635 argv[1] = NULL;
636 }
637
638 msgset_parser_init (&parser, mbox, argc, argv);
639 msgset_parser_run (&parser);
640 *msgset = parser.msgset;
641 }
642
643 void
mh_msgset_parse_string(mu_msgset_t * msgset,mu_mailbox_t mbox,const char * string,const char * def)644 mh_msgset_parse_string (mu_msgset_t *msgset, mu_mailbox_t mbox,
645 const char *string, const char *def)
646 {
647 struct mu_wordsplit ws;
648
649 if (mu_wordsplit (string, &ws, MU_WRDSF_DEFFLAGS))
650 {
651 mu_error (_("cannot split line `%s': %s"), string,
652 mu_wordsplit_strerror (&ws));
653 exit (1);
654 }
655 mh_msgset_parse (msgset, mbox, ws.ws_wordc, ws.ws_wordv, def);
656 mu_wordsplit_free (&ws);
657 }
658
659
660 /* Retrieve the message with the given sequence number.
661 Returns ordinal number of the message in the mailbox if found,
662 zero otherwise. The retrieved message is stored in the location
663 pointed to by mesg, unless it is NULL. */
664
665 size_t
mh_get_message(mu_mailbox_t mbox,size_t seqno,mu_message_t * mesg)666 mh_get_message (mu_mailbox_t mbox, size_t seqno, mu_message_t *mesg)
667 {
668 int rc;
669 size_t num;
670
671 if (mu_mailbox_translate (mbox, MU_MAILBOX_UID_TO_MSGNO, seqno, &num))
672 return 0;
673 if (mesg)
674 {
675 rc = mu_mailbox_get_message (mbox, num, mesg);
676 if (rc)
677 {
678 mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_get_message", NULL, rc);
679 exit (1);
680 }
681 }
682 return num;
683 }
684