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