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