1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2005-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 burst command */
18 
19 #include <mh.h>
20 
21 static char prog_doc[] = N_("Explode digests into messages");
22 static char args_doc[] = N_("[MSGLIST]");
23 
24 /* Command line switches */
25 int inplace;
26 int quiet;
27 int verbose;
28 int recursive;
29 int eb_min_length = 1;  /* Minimal length of encapsulation boundary */
30 
31 #define VERBOSE(c) do { if (verbose) { printf c; putchar ('\n'); } } while (0)
32 
33 static struct mu_option options[] = {
34   { "inplace",     0,      NULL, MU_OPTION_DEFAULT,
35    N_("replace the source message with the table of contents, insert extracted messages after it"),
36     mu_c_bool, &inplace },
37   { "quiet",       0,      NULL, MU_OPTION_DEFAULT,
38     N_("be quiet about the messages that are not in digest format"),
39     mu_c_bool, &quiet },
40   { "verbose",     0,      NULL, MU_OPTION_DEFAULT,
41     N_("verbosely list the actions taken"),
42     mu_c_bool, &verbose },
43   { "recursive",   0,      NULL, MU_OPTION_DEFAULT,
44     N_("recursively expand MIME messages"),
45     mu_c_bool, &recursive },
46   { "length",      0, N_("NUM"), MU_OPTION_DEFAULT,
47     N_("set minimal length of digest encapsulation boundary (default 1)"),
48     mu_c_int, &eb_min_length },
49   MU_OPTION_END
50 };
51 
52 /* General-purpose data structures */
53 struct burst_map
54 {
55   int mime;            /* Is mime? */
56   size_t msgno;        /* Number of the original message */
57   /* Following numbers refer to tmpbox */
58   size_t first;        /* Number of the first bursted message */
59   size_t count;        /* Number of bursted messages */
60 };
61 
62 
63 /* Global data */
64 struct burst_map map;        /* Currently built map */
65 struct burst_map *burst_map; /* Finished burst map */
66 size_t burst_count;          /* Number of items in burst_map */
67 mu_mailbox_t tmpbox;         /* Temporary mailbox */
68 mu_opool_t pool;             /* Object pool for building burst_map, etc. */
69 
70 static int burst_or_copy (mu_message_t msg, int recursive, int copy);
71 
72 
73 /* MIME messages */
74 int
burst_mime(mu_message_t msg)75 burst_mime (mu_message_t msg)
76 {
77   size_t i, nparts;
78   int rc;
79 
80   rc = mu_message_get_num_parts (msg, &nparts);
81   if (rc)
82     {
83       mu_diag_funcall (MU_DIAG_ERR, "mu_message_get_num_parts", NULL, rc);
84       return rc;
85     }
86 
87   for (i = 1; i <= nparts; i++)
88     {
89       mu_message_t mpart;
90       if (mu_message_get_part (msg, i, &mpart) == 0)
91 	{
92 	  if (!map.first)
93 	    mu_mailbox_uidnext (tmpbox, &map.first);
94 	  burst_or_copy (mpart, recursive, 1);
95 	}
96     }
97   return 0;
98 }
99 
100 
101 /* Digest messages */
102 
103 /* Bursting FSA states accoring to RFC 934:
104 
105       S1 ::   "-" S3
106             | CRLF {CRLF} S1
107             | c {c} S2
108 
109       S2 ::   CRLF {CRLF} S1
110             | c {c} S2
111 
112       S3 ::   " " S2
113             | c S4     ;; the bursting agent should consider the current
114 	               ;; message ended.
115 
116       S4 ::   CRLF S5
117             | c S4
118 
119       S5 ::   CRLF S5
120             | c {c} S2 ;; The bursting agent should consider a new
121 	               ;; message started
122 */
123 
124 #define S1 1
125 #define S2 2
126 #define S3 3
127 #define S4 4
128 #define S5 5
129 
130 /* Negative state means no write */
131 int transtab[5][256] = {
132 /* S1 */ {  S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
133             S2,  S2,  S1,  S2,  S2,  S2,  S2,  S2,
134             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
135             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
136             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
137             S2,  S2,  S2,  S2,  S2, -S3,  S2,  S2,
138             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
139             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
140             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
141             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
142             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
143             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
144             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
145             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
146             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
147             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
148             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
149             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
150             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
151             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
152             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
153             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
154             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
155             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
156             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
157             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
158             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
159             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
160             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
161             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
162             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
163             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2 },
164 /* S2 */ {  S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
165             S2,  S2,  S1,  S2,  S2,  S2,  S2,  S2,
166             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
167             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
168             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
169             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
170             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
171             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
172             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
173             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
174             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
175             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
176             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
177             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
178             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
179             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
180             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
181             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
182             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
183             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
184             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
185             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
186             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
187             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
188             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
189             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
190             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
191             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
192             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
193             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
194             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
195             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2 },
196 /* S3 */ { -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
197            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
198            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
199            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
200            -S2, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
201            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
202            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
203            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
204            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
205            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
206            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
207            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
208            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
209            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
210            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
211            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
212            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
213            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
214            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
215            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
216            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
217            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
218            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
219            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
220            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
221            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
222            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
223            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
224            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
225            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
226            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
227            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4 },
228 /* S4 */ { -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
229            -S4, -S4, -S5, -S4, -S4, -S4, -S4, -S4,
230            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
231            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
232            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
233            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
234            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
235            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
236            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
237            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
238            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
239            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
240            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
241            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
242            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
243            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
244            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
245            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
246            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
247            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
248            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
249            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
250            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
251            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
252            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
253            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
254            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
255            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
256            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
257            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
258            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4,
259            -S4, -S4, -S4, -S4, -S4, -S4, -S4, -S4 },
260 /* S5 */ {  S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
261             S2,  S2, -S5,  S2,  S2,  S2,  S2,  S2,
262             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
263             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
264             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
265             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
266             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
267             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
268             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
269             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
270             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
271             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
272             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
273             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
274             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
275             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
276             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
277             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
278             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
279             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
280             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
281             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
282             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
283             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
284             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
285             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
286             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
287             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
288             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
289             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
290             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2,
291             S2,  S2,  S2,  S2,  S2,  S2,  S2,  S2 }
292 };
293 
294 #define F_FIRST  0x01  /* First part of the message (no EB seen so far) */
295 #define F_ENCAPS 0x02  /* Within encapsulated part */
296 
297 struct burst_stream
298 {
299   mu_stream_t stream;  /* Output stream */
300   int flags;           /* See F_ flags above */
301   size_t msgno;        /* Number of the current message */
302   size_t partno;       /* Number of the part within the message */
303 };
304 
305 static inline void
finish_stream(struct burst_stream * bs)306 finish_stream (struct burst_stream *bs)
307 {
308   if (bs->stream)
309     {
310       mu_message_t msg;
311 
312       mu_stream_seek (bs->stream, 0, SEEK_SET, NULL);
313       msg = mh_stream_to_message (bs->stream);
314       if (!map.first)
315 	mu_mailbox_uidnext (tmpbox, &map.first);
316       burst_or_copy (msg, recursive, 1);
317       mu_message_destroy (&msg, mu_message_get_owner (msg));
318       bs->stream = 0;
319 
320       bs->partno++;
321       bs->flags &= ~F_FIRST;
322     }
323 }
324 
325 static inline void
flush_stream(struct burst_stream * bs,char * buf,size_t size)326 flush_stream (struct burst_stream *bs, char *buf, size_t size)
327 {
328   int rc;
329 
330   if (size == 0)
331     return;
332   if (!bs->stream)
333     {
334       if ((rc = mu_temp_stream_create (&bs->stream, 0)))
335 	{
336 	  mu_error (_("Cannot open temporary file: %s"),
337 		    mu_strerror (rc));
338 	  exit (1);
339 	}
340       mu_stream_printf (bs->stream, "X-Burst-Part: %lu %lu %02x\n",
341 			(unsigned long) bs->msgno,
342 			(unsigned long) bs->partno, bs->flags);
343       if (!bs->flags)
344 	mu_stream_write (bs->stream, "\n", 1, NULL);
345 
346       if (verbose && !inplace)
347 	{
348 	  size_t nextuid;
349 	  mu_mailbox_uidnext (tmpbox, &nextuid);
350 	  printf (_("message %lu of digest %lu becomes message %lu\n"),
351 		  (unsigned long) bs->partno,
352 		  (unsigned long) bs->msgno,
353 		  (unsigned long) nextuid);
354 	}
355     }
356   rc = mu_stream_write (bs->stream, buf, size, NULL);
357   if (rc)
358     {
359       mu_error (_("error writing temporary stream: %s"),
360 		mu_strerror (rc));
361       exit (1); /* FIXME: better error handling please */
362     }
363 }
364 
365 /* Burst an RFC 934 digest.  Return 0 if OK, 1 if the message is not
366    a valid digest.
367    FIXME: On errors, cleanup and return -1.
368 */
369 int
burst_digest(mu_message_t msg)370 burst_digest (mu_message_t msg)
371 {
372   mu_stream_t is;
373   unsigned char c;
374   size_t n;
375   int state = S1;
376   int eb_length = 0;
377   struct burst_stream bs;
378   int result = 0;
379 
380   bs.stream = NULL;
381   bs.flags = F_FIRST;
382   bs.partno = 1;
383   mh_message_number (msg, &bs.msgno);
384 
385   mu_message_get_streamref (msg, &is);
386   while (mu_stream_read (is, &c, 1, &n) == 0 && n == 1)
387     {
388       int newstate = transtab[state - 1][c];
389       int silent = 0;
390 
391       if (newstate < 0)
392 	{
393 	  newstate = -newstate;
394 	  silent = 1;
395 	}
396 
397       if (state == S1)
398 	{
399 	  /* GNU extension: check if we have seen enough dashes to
400 	     constitute a valid encapsulation boundary. */
401 	  if (newstate == S3)
402 	    {
403 	      eb_length++;
404 	      if (eb_length < eb_min_length)
405 		continue; /* Ignore state change */
406 	      if (eb_min_length > 1)
407 		{
408 		  newstate = S4;
409 		  finish_stream (&bs);
410 		  bs.flags ^= F_ENCAPS;
411 		}
412 	    }
413 	  else
414 	    for (; eb_length; eb_length--)
415 	      flush_stream (&bs, "-", 1);
416 	  eb_length = 0;
417 	}
418       else if (state == S5 && newstate == S2)
419 	{
420 	  /* As the automaton traverses from state S5 to S2, the
421 	     bursting agent should consider a new message started
422 	     and output the first character. */
423 	  finish_stream (&bs);
424 	}
425       else if (state == S3 && newstate == S4)
426 	{
427 	  /* As the automaton traverses from state S3 to S4, the
428 	     bursting agent should consider the current message ended. */
429 	  finish_stream (&bs);
430 	  bs.flags ^= F_ENCAPS;
431 	}
432       state = newstate;
433       if (!silent)
434 	flush_stream (&bs, (char*)&c, 1);
435     }
436   mu_stream_destroy (&is);
437 
438   if (bs.flags == F_FIRST)
439     {
440       mu_stream_destroy (&bs.stream);
441       result = 1;
442     }
443   else if (bs.stream)
444     {
445       mu_off_t size = 0;
446       mu_stream_size (bs.stream, &size);
447       if (size)
448 	finish_stream (&bs);
449       else
450 	mu_stream_destroy (&bs.stream);
451     }
452   return result;
453 }
454 
455 
456 int
burst_or_copy(mu_message_t msg,int recursive,int copy)457 burst_or_copy (mu_message_t msg, int recursive, int copy)
458 {
459   if (recursive)
460     {
461       int mime = 0;
462       mu_message_is_multipart (msg, &mime);
463 
464       if (mime)
465 	{
466 	  if (!map.first)
467 	    map.mime = 1;
468 	  return burst_mime (msg);
469 	}
470       else if (burst_digest (msg) == 0)
471 	return 0;
472     }
473 
474   if (copy)
475     {
476       int rc;
477 
478       if (map.mime)
479 	{
480 	  mu_header_t hdr;
481 	  char *value = NULL;
482 
483 	  mu_message_get_header (msg, &hdr);
484 	  if (mu_header_aget_value (hdr, MU_HEADER_CONTENT_TYPE, &value) == 0
485 	      && memcmp (value, "message/rfc822", 14) == 0)
486 	    {
487 	      mu_stream_t str;
488 	      mu_body_t body;
489 
490 	      mu_message_get_body (msg, &body);
491 	      mu_body_get_streamref (body, &str);
492 	      msg = mh_stream_to_message (str);
493 	    }
494 	  free (value);
495 	}
496 
497       /* FIXME:
498 	 if (verbose && !inplace)
499 	   printf(_("message %lu of digest %lu becomes message %s"),
500 		   (unsigned long) (j+1),
501 		   (unsigned long) burst_map[i].msgno, to));
502       */
503 
504       rc = mu_mailbox_append_message (tmpbox, msg);
505       if (rc)
506 	{
507 	  mu_error (_("cannot append message: %s"), mu_strerror (rc));
508 	  exit (1);
509 	}
510       map.count++;
511       return 0;
512     }
513 
514   return 1;
515 }
516 
517 int
burst(size_t num,mu_message_t msg,void * data)518 burst (size_t num, mu_message_t msg, void *data)
519 {
520   memset (&map, 0, sizeof (map));
521   mh_message_number (msg, &map.msgno);
522 
523   if (burst_or_copy (msg, 1, 0) == 0)
524     {
525       VERBOSE((ngettext ("%s message exploded from digest %s",
526 			 "%s messages exploded from digest %s",
527 			 (unsigned long) map.count),
528 	       mu_umaxtostr (0, map.count),
529 	       mu_umaxtostr (1, num)));
530       if (inplace)
531 	{
532 	  mu_opool_append (pool, &map, sizeof map);
533 	  burst_count++;
534 	}
535     }
536   else if (!quiet)
537     mu_error (_("message %s not in digest format"), mu_umaxtostr (0, num));
538   return 0;
539 }
540 
541 
542 /* Inplace handling */
543 struct rename_env
544 {
545   size_t lastuid;
546   size_t idx;
547 };
548 
549 
550 static int
_rename(size_t msgno,void * data)551 _rename (size_t msgno, void *data)
552 {
553   struct rename_env *rp = data;
554 
555   if (msgno == burst_map[rp->idx].msgno)
556     {
557       rp->lastuid -= burst_map[rp->idx].count;
558       burst_map[rp->idx].msgno = rp->lastuid;
559       rp->idx--;
560     }
561 
562   if (msgno != rp->lastuid)
563     {
564       const char *from;
565       const char *to;
566 
567       from = mu_umaxtostr (0, msgno);
568       to   = mu_umaxtostr (1, rp->lastuid);
569       --rp->lastuid;
570 
571       VERBOSE((_("message %s becomes message %s"), from, to));
572 
573       if (rename (from, to))
574 	{
575 	  mu_error (_("error renaming %s to %s: %s"),
576 		    from, to, mu_strerror (errno));
577 	  exit (1);
578 	}
579     }
580   return 0;
581 }
582 
583 void
burst_rename(mu_msgset_t ms,size_t lastuid)584 burst_rename (mu_msgset_t ms, size_t lastuid)
585 {
586   struct rename_env renv;
587 
588   VERBOSE ((_("Renaming messages")));
589   renv.lastuid = lastuid;
590   renv.idx = burst_count - 1;
591   mu_msgset_foreach_dir_msguid (ms, 1, _rename, &renv);
592 }
593 
594 void
msg_copy(size_t num,const char * file)595 msg_copy (size_t num, const char *file)
596 {
597   mu_message_t msg;
598   mu_attribute_t attr = NULL;
599   mu_stream_t istream, ostream;
600   int rc;
601 
602   if ((rc = mu_file_stream_create (&ostream,
603 				   file,
604 				   MU_STREAM_WRITE|MU_STREAM_CREAT)))
605     {
606       mu_error (_("Cannot open output file `%s': %s"),
607 		file, mu_strerror (rc));
608       exit (1);
609     }
610 
611   mu_mailbox_get_message (tmpbox, num, &msg);
612   mu_message_get_streamref (msg, &istream);
613   rc = mu_stream_copy (ostream, istream, 0, NULL);
614   if (rc)
615     {
616       mu_error (_("copy stream error: %s"), mu_strerror (rc));
617       exit (1);
618     }
619 
620   mu_stream_destroy (&istream);
621 
622   mu_stream_close (ostream);
623   mu_stream_destroy (&ostream);
624 
625   /* Mark message as deleted */
626   mu_message_get_attribute (msg, &attr);
627   mu_attribute_set_deleted (attr);
628 }
629 
630 void
finalize_inplace(size_t lastuid)631 finalize_inplace (size_t lastuid)
632 {
633   size_t i;
634 
635   VERBOSE ((_("Moving bursted out messages in place")));
636 
637   for (i = 0; i < burst_count; i++)
638     {
639       size_t j;
640 
641       /* FIXME: toc handling */
642       for (j = 0; j < burst_map[i].count; j++)
643 	{
644 	  const char *to = mu_umaxtostr (0, burst_map[i].msgno + 1 + j);
645 	  VERBOSE((_("message %s of digest %s becomes message %s"),
646 		   mu_umaxtostr (1, j + 1),
647 		   mu_umaxtostr (2, burst_map[i].msgno), to));
648 	  msg_copy (burst_map[i].first + j, to);
649 	}
650     }
651 }
652 
653 int
main(int argc,char ** argv)654 main (int argc, char **argv)
655 {
656   int rc;
657   mu_mailbox_t mbox;
658   mu_msgset_t msgset;
659   const char *tempfolder = NULL;
660 
661   mh_getopt (&argc, &argv, options, MH_GETOPT_DEFAULT_FOLDER,
662 	     args_doc, prog_doc, NULL);
663   if (!tempfolder)
664     tempfolder = mh_global_profile_get ("Temp-Folder", ".temp");
665   if (eb_min_length == 0)
666     eb_min_length = 1;
667 
668   VERBOSE ((_("Opening folder `%s'"), mh_current_folder ()));
669   mbox = mh_open_folder (mh_current_folder (), MU_STREAM_RDWR);
670   mh_msgset_parse (&msgset, mbox, argc, argv, "cur");
671 
672   if (inplace)
673     {
674       size_t i, count;
675 
676       VERBOSE ((_("Opening temporary folder `%s'"), tempfolder));
677       tmpbox = mh_open_folder (tempfolder, MU_STREAM_RDWR|MU_STREAM_CREAT);
678       VERBOSE ((_("Cleaning up temporary folder")));
679       mu_mailbox_messages_count (tmpbox, &count);
680       for (i = 1; i <= count; i++)
681 	{
682 	  mu_attribute_t attr = NULL;
683 	  mu_message_t msg = NULL;
684 	  mu_mailbox_get_message (tmpbox, i, &msg);
685 	  mu_message_get_attribute (msg, &attr);
686 	  mu_attribute_set_deleted (attr);
687 	}
688       mu_mailbox_expunge (tmpbox);
689       mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT);
690     }
691   else
692     tmpbox = mbox;
693 
694   rc = mu_msgset_foreach_message (msgset, burst, NULL);
695   if (rc)
696     return rc;
697 
698   if (inplace && burst_count)
699     {
700       mu_url_t dst_url = NULL;
701       size_t i, next_uid, last_uid;
702       mu_msgset_t ms;
703       size_t count;
704       const char *dir;
705 
706       burst_map = mu_opool_finish (pool, NULL);
707 
708       mu_mailbox_uidnext (mbox, &next_uid);
709       for (i = 0, last_uid = next_uid-1; i < burst_count; i++)
710 	last_uid += burst_map[i].count;
711       VERBOSE ((_("Estimated last UID: %s"), mu_umaxtostr (0, last_uid)));
712 
713       rc = mu_msgset_create (&ms, mbox, MU_MSGSET_NUM);
714       if (rc)
715 	{
716 	  mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_create", NULL, rc);
717 	  exit (1);
718 	}
719       mu_mailbox_messages_count (mbox, &count);
720       mu_msgset_add_range (ms, burst_map[0].msgno, count, MU_MSGSET_NUM);
721 
722       mu_mailbox_get_url (mbox, &dst_url);
723       mu_url_sget_path (dst_url, &dir);
724       VERBOSE ((_("changing to `%s'"), dir));
725       if (chdir (dir))
726 	{
727 	  mu_error (_("cannot change to `%s': %s"), dir, mu_strerror (errno));
728 	  exit (1);
729 	}
730       mu_mailbox_close (mbox);
731 
732       burst_rename (ms, last_uid);
733       mu_msgset_free (ms);
734 
735       finalize_inplace (last_uid);
736 
737       VERBOSE ((_("Expunging temporary folder")));
738       mu_mailbox_expunge (tmpbox);
739       mu_mailbox_close (tmpbox);
740       mu_mailbox_destroy (&tmpbox);
741     }
742   else
743     mu_mailbox_close (mbox);
744 
745   mu_mailbox_destroy (&mbox);
746   VERBOSE ((_("Finished bursting")));
747   return rc;
748 }
749 
750 
751 
752