Added mhmail.in. start of a replacement for compiled mhmail.
[mmh] / uip / burst.c
1
2 /*
3  * burst.c -- explode digests into individual messages
4  *
5  * This code is Copyright (c) 2002, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 #include <h/mh.h>
11
12 static struct swit switches[] = {
13 #define INPLSW  0
14     { "inplace", 0 },
15 #define NINPLSW 1
16     { "noinplace", 0 },
17 #define QIETSW  2
18     { "quiet", 0 },
19 #define NQIETSW 3
20     { "noquiet", 0 },
21 #define VERBSW  4
22     { "verbose", 0 },
23 #define NVERBSW 5
24     { "noverbose", 0 },
25 #define VERSIONSW 6
26     { "version", 0 },
27 #define HELPSW  7
28     { "help", 0 },
29     { NULL, 0 }
30 };
31
32 struct smsg {
33     off_t s_start;
34     off_t s_stop;
35 };
36
37 /*
38  * static prototypes
39  */
40 static int find_delim (int, struct smsg *);
41 static void burst (struct msgs **, int, struct smsg *, int, int, int, char *);
42 static void cpybrst (FILE *, FILE *, char *, char *, int);
43
44 /*
45  * A macro to check to see if we have reached a message delimiter
46  * (an encapsulation boundary, EB, in RFC 934 parlance).
47  *
48  * According to RFC 934, an EB is simply a line which starts with
49  * a "-" and is NOT followed by a space.  So even a single "-" on a line
50  * by itself would be an EB.
51  */
52
53 #define CHECKDELIM(buffer) (buffer[0] == '-' && buffer[1] != ' ')
54
55 int
56 main (int argc, char **argv)
57 {
58     int inplace = 0, quietsw = 0, verbosw = 0;
59     int msgp = 0, hi, msgnum, numburst;
60     char *cp, *maildir, *folder = NULL, buf[BUFSIZ];
61     char **argp, **arguments, *msgs[MAXARGS];
62     struct smsg *smsgs;
63     struct msgs *mp;
64
65 #ifdef LOCALE
66     setlocale(LC_ALL, "");
67 #endif
68     invo_name = r1bindex (argv[0], '/');
69
70     /* read user profile/context */
71     context_read();
72
73     arguments = getarguments (invo_name, argc, argv, 1);
74     argp = arguments;
75
76     while ((cp = *argp++)) {
77         if (*cp == '-') {
78             switch (smatch (++cp, switches)) {
79             case AMBIGSW: 
80                 ambigsw (cp, switches);
81                 done (1);
82             case UNKWNSW: 
83                 adios (NULL, "-%s unknown\n", cp);
84
85             case HELPSW: 
86                 snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
87                         invo_name);
88                 print_help (buf, switches, 1);
89                 done (0);
90             case VERSIONSW:
91                 print_version(invo_name);
92                 done (0);
93
94             case INPLSW: 
95                 inplace++;
96                 continue;
97             case NINPLSW: 
98                 inplace = 0;
99                 continue;
100
101             case QIETSW: 
102                 quietsw++;
103                 continue;
104             case NQIETSW: 
105                 quietsw = 0;
106                 continue;
107
108             case VERBSW: 
109                 verbosw++;
110                 continue;
111             case NVERBSW: 
112                 verbosw = 0;
113                 continue;
114             }
115         }
116         if (*cp == '+' || *cp == '@') {
117             if (folder)
118                 adios (NULL, "only one folder at a time!");
119             else
120                 folder = pluspath (cp);
121         } else {
122             msgs[msgp++] = cp;
123         }
124     }
125
126     if (!context_find ("path"))
127         free (path ("./", TFOLDER));
128     if (!msgp)
129         msgs[msgp++] = "cur";
130     if (!folder)
131         folder = getfolder (1);
132     maildir = m_maildir (folder);
133
134     if (chdir (maildir) == NOTOK)
135         adios (maildir, "unable to change directory to");
136
137     /* read folder and create message structure */
138     if (!(mp = folder_read (folder)))
139         adios (NULL, "unable to read folder %s", folder);
140
141     /* check for empty folder */
142     if (mp->nummsg == 0)
143         adios (NULL, "no messages in %s", folder);
144
145     /* parse all the message ranges/sequences and set SELECTED */
146     for (msgnum = 0; msgnum < msgp; msgnum++)
147         if (!m_convert (mp, msgs[msgnum]))
148             done (1);
149     seq_setprev (mp);   /* set the previous-sequence */
150
151     smsgs = (struct smsg *)
152         calloc ((size_t) (MAXFOLDER + 2), sizeof(*smsgs));
153     if (smsgs == NULL)
154         adios (NULL, "unable to allocate burst storage");
155
156     hi = mp->hghmsg + 1;
157
158     /* burst all the SELECTED messages */
159     for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
160         if (is_selected (mp, msgnum)) {
161             if ((numburst = find_delim (msgnum, smsgs)) >= 1) {
162                 if (verbosw)
163                     printf ("%d message%s exploded from digest %d\n",
164                             numburst, numburst > 1 ? "s" : "", msgnum);
165                 burst (&mp, msgnum, smsgs, numburst, inplace, verbosw, maildir);
166             } else {
167                 if (numburst == 0) {
168                     if (!quietsw)
169                         admonish (NULL, "message %d not in digest format",
170                                   msgnum);
171                 }  /* this pair of braces was missing before 1999-07-15 */
172                 else
173                     adios (NULL, "burst() botch -- you lose big");
174             }
175         }
176     }
177
178     free ((char *) smsgs);
179     context_replace (pfolder, folder);  /* update current folder */
180
181     /*
182      * If -inplace is given, then the first message burst becomes
183      * the current message (which will now show a table of contents).
184      * Otherwise, the first message extracted from the first digest
185      * becomes the current message.
186      */
187     if (inplace) {
188         if (mp->lowsel != mp->curmsg)
189             seq_setcur (mp, mp->lowsel);
190     } else {
191         if (hi <= mp->hghmsg)
192             seq_setcur (mp, hi);
193     }
194
195     seq_save (mp);      /* synchronize message sequences */
196     context_save ();    /* save the context file         */
197     folder_free (mp);   /* free folder/message structure */
198     done (0);
199     return 1;
200 }
201
202
203 /*
204  * Scan the message and find the beginning and
205  * end of all the messages in the digest.
206  */
207
208 static int
209 find_delim (int msgnum, struct smsg *smsgs)
210 {
211     int wasdlm = 0, msgp;
212     off_t pos;
213     char c, *msgnam;
214     char buffer[BUFSIZ];
215     FILE *in;
216
217     if ((in = fopen (msgnam = m_name (msgnum), "r")) == NULL)
218         adios (msgnam, "unable to read message");
219
220     for (msgp = 0, pos = 0L; msgp <= MAXFOLDER;) {
221         /*
222          * We're either at the beginning of the whole message, or
223          * we're just past the delimiter of the last message.
224          * Swallow lines until we get to something that's not a newline
225          */
226         while (fgets (buffer, sizeof(buffer), in) && buffer[0] == '\n')
227             pos += (long) strlen (buffer);
228         if (feof (in))
229             break;
230
231         /*
232          * Reset to the beginning of the last non-blank line, and save our
233          * starting position.  This is where the encapsulated message
234          * starts.
235          */
236         fseeko (in, pos, SEEK_SET);
237         smsgs[msgp].s_start = pos;
238
239         /*
240          * Read in lines until we get to a message delimiter.
241          *
242          * Previously we checked to make sure the preceeding line and
243          * next line was a newline.  That actually does not comply with
244          * RFC 934, so make sure we break on a message delimiter even
245          * if the previous character was NOT a newline.
246          */
247         for (c = 0; fgets (buffer, sizeof(buffer), in); c = buffer[0]) {
248             if ((wasdlm = CHECKDELIM(buffer)))
249                 break;
250             else
251                 pos += (long) strlen (buffer);
252         }
253
254         /*
255          * Only count as a new message if we got the message delimiter.
256          * Swallow a blank line if it was right before the message delimiter.
257          */
258         if (smsgs[msgp].s_start != pos && wasdlm)
259             smsgs[msgp++].s_stop = (c == '\n' && wasdlm) ? pos - 1 : pos;
260
261         if (feof (in)) {
262 #if 0
263             if (wasdlm) {
264                 smsgs[msgp - 1].s_stop -= ((long) strlen (buffer) + 1);
265                 msgp++;         /* fake "End of XXX Digest" */
266             }
267 #endif
268             break;
269         }
270         pos += (long) strlen (buffer);
271     }
272
273     fclose (in);
274     return (msgp - 1);          /* return the number of messages burst */
275 }
276
277
278 /*
279  * Burst out the messages in the digest into the folder
280  */
281
282 static void
283 burst (struct msgs **mpp, int msgnum, struct smsg *smsgs, int numburst,
284         int inplace, int verbosw, char *maildir)
285 {
286     int i, j, mode;
287     char *msgnam;
288     char f1[BUFSIZ], f2[BUFSIZ], f3[BUFSIZ];
289     FILE *in, *out;
290     struct stat st;
291     struct msgs *mp;
292
293     if ((in = fopen (msgnam = m_name (msgnum), "r")) == NULL)
294         adios (msgnam, "unable to read message");
295
296     mode =
297       fstat (fileno(in), &st) != NOTOK ? (int) (st.st_mode & 0777) : m_gmprot();
298     mp = *mpp;
299
300     /*
301      * See if we have enough space in the folder
302      * structure for all the new messages.
303      */
304     if ((mp->hghmsg + numburst > mp->hghoff) &&
305         !(mp = folder_realloc (mp, mp->lowoff, mp->hghmsg + numburst)))
306         adios (NULL, "unable to allocate folder storage");
307     *mpp = mp;
308
309     j = mp->hghmsg;             /* old value */
310     mp->hghmsg += numburst;
311     mp->nummsg += numburst;
312
313     /*
314      * If this is not the highest SELECTED message, then
315      * increment mp->hghsel by numburst, since the highest
316      * SELECTED is about to be slid down by that amount.
317      */
318     if (msgnum < mp->hghsel)
319         mp->hghsel += numburst;
320
321     /*
322      * If -inplace is given, renumber the messages after the
323      * source message, to make room for each of the messages
324      * contained within the digest.
325      *
326      * This is equivalent to refiling a message from the point
327      * of view of the external hooks.
328      */
329     if (inplace) {
330         for (i = mp->hghmsg; j > msgnum; i--, j--) {
331             strncpy (f1, m_name (i), sizeof(f1));
332             strncpy (f2, m_name (j), sizeof(f2));
333             if (does_exist (mp, j)) {
334                 if (verbosw)
335                     printf ("message %d becomes message %d\n", j, i);
336
337                 if (rename (f2, f1) == NOTOK)
338                     admonish (f1, "unable to rename %s to", f2);
339
340                 (void)snprintf(f1, sizeof (f1), "%s/%d", maildir, i);
341                 (void)snprintf(f2, sizeof (f2), "%s/%d", maildir, j);
342                 ext_hook("ref-hook", f1, f2);
343
344                 copy_msg_flags (mp, i, j);
345                 clear_msg_flags (mp, j);
346                 mp->msgflags |= SEQMOD;
347             }
348         }
349     }
350     
351     unset_selected (mp, msgnum);
352
353     /* new hghmsg is hghmsg + numburst
354      *
355      * At this point, there is an array of numburst smsgs, each element of
356      * which contains the starting and stopping offsets (seeks) of the message
357      * in the digest.  The inplace flag is set if the original digest is replaced
358      * by a message containing the table of contents.  smsgs[0] is that table of
359      * contents.  Go through the message numbers in reverse order (high to low).
360      *
361      * Set f1 to the name of the destination message, f2 to the name of a scratch
362      * file.  Extract a message from the digest to the scratch file.  Move the
363      * original message to a backup file if the destination message number is the
364      * same as the number of the original message, which only happens if the
365      * inplace flag is set.  Then move the scratch file to the destination message.
366      *
367      * Moving the original message to the backup file is equivalent to deleting the
368      * message from the point of view of the external hooks.  And bursting each
369      * message is equivalent to adding a new message.
370      */
371
372     i = inplace ? msgnum + numburst : mp->hghmsg;
373     for (j = numburst; j >= (inplace ? 0 : 1); i--, j--) {
374         strncpy (f1, m_name (i), sizeof(f1));
375         strncpy (f2, m_mktemp(invo_name, NULL, &out), sizeof(f2));
376
377         if (verbosw && i != msgnum)
378             printf ("message %d of digest %d becomes message %d\n", j, msgnum, i);
379
380         chmod (f2, mode);
381         fseeko (in, smsgs[j].s_start, SEEK_SET);
382         cpybrst (in, out, msgnam, f2,
383                 (int) (smsgs[j].s_stop - smsgs[j].s_start));
384         fclose (out);
385
386         if (i == msgnum) {
387             strncpy (f3, m_backup (f1), sizeof(f3));
388             if (rename (f1, f3) == NOTOK)
389                 admonish (f3, "unable to rename %s to", f1);
390
391             (void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
392             ext_hook("del-hook", f3, (char *)0);
393         }
394         if (rename (f2, f1) == NOTOK)
395             admonish (f1, "unable to rename %s to", f2);
396
397         (void)snprintf(f3, sizeof (f3), "%s/%d", maildir, i);
398         ext_hook("add-hook", f3, (char *)0);
399
400         copy_msg_flags (mp, i, msgnum);
401         mp->msgflags |= SEQMOD;
402     }
403
404     fclose (in);
405 }
406
407
408 #define S1  0
409 #define S2  1
410 #define S3  2
411
412 /*
413  * Copy a mesage which is being burst out of a digest.
414  * It will remove any "dashstuffing" in the message.
415  */
416
417 static void
418 cpybrst (FILE *in, FILE *out, char *ifile, char *ofile, int len)
419 {
420     register int c, state;
421
422     for (state = S1; (c = fgetc (in)) != EOF && len > 0; len--) {
423         if (c == 0)
424             continue;
425         switch (state) {
426             case S1: 
427                 switch (c) {
428                     case '-': 
429                         state = S3;
430                         break;
431
432                     default: 
433                         state = S2;
434                     case '\n': 
435                         fputc (c, out);
436                         break;
437                 }
438                 break;
439
440             case S2: 
441                 switch (c) {
442                     case '\n': 
443                         state = S1;
444                     default: 
445                         fputc (c, out);
446                         break;
447                 }
448                 break;
449
450             case S3: 
451                 switch (c) {
452                     case ' ': 
453                         state = S2;
454                         break;
455
456                     default: 
457                         state = (c == '\n') ? S1 : S2;
458                         fputc ('-', out);
459                         fputc (c, out);
460                         break;
461                 }
462                 break;
463         }
464     }
465
466     if (ferror (in) && !feof (in))
467         adios (ifile, "error reading");
468     if (ferror (out))
469         adios (ofile, "error writing");
470 }