]> Pileus Git - ~andy/fetchmail/blob - rfc822.c
Another step forwards.
[~andy/fetchmail] / rfc822.c
1 /*
2  * rfc822.c -- code for slicing and dicing RFC822 mail headers
3  *
4  * Copyright 1996 by Eric S. Raymond
5  * All rights reserved.
6  * For license terms, see the file COPYING in this directory.
7  */
8
9 #include  <stdio.h>
10 #include  <ctype.h>
11 #include  <string.h>
12 #if defined(STDC_HEADERS)
13 #include  <stdlib.h>
14 #endif
15
16 #include  "fetchmail.h"
17
18 void reply_hack(buf, host)
19 /* hack message headers so replies will work properly */
20 char *buf;              /* header to be hacked */
21 const char *host;       /* server hostname */
22 {
23     const char *from;
24     int parendepth, oldstate, state = 0, tokencount = 0, nlterm = 0;
25     char mycopy[POPBUFSIZE+1];
26
27     if (strncmp("From: ", buf, 6)
28         && strncmp("To: ", buf, 4)
29         && strncmp("Reply-", buf, 6)
30         && strncmp("Cc: ", buf, 4)
31         && strncmp("Bcc: ", buf, 5)) {
32         return;
33     }
34
35     strcpy(mycopy, buf);
36     if (mycopy[strlen(mycopy) - 1] == '\n')
37     {
38         mycopy[strlen(mycopy) - 1] = '\0';
39         nlterm = TRUE;
40     }
41     if (mycopy[strlen(mycopy) - 1] != ',')
42         strcat(mycopy, ",");
43
44     for (from = mycopy; *from; from++)
45     {
46 #ifdef FOO
47         printf("state %d: %s", state, mycopy);
48         printf("%*s^\n", from - mycopy + 10, " ");
49 #endif /* TESTMAIN */
50
51 #define INSERT_HOSTNAME \
52                 strcpy(buf, "@"); \
53                 strcat(buf, host); \
54                 buf += strlen(buf); \
55                 state = 7;
56
57         switch (state)
58         {
59         case 0:   /* before header colon */
60             if (*from == ':')
61                 state = 1;
62             break;
63
64         case 1:   /* we've seen the colon, we're looking for addresses */
65             if (*from == '"')
66                 state = 3;
67             else if (*from == '(')
68             {
69                 parendepth = 1;
70                 oldstate = 1;
71                 state = 4;    
72             }
73             else if (*from == '<')
74                 state = 5;
75             else if (isalnum(*from))
76                 state = 6;
77             else if (isspace(*from))
78                 state = 2;
79             break;
80
81         case 2:     /* found a token boundary -- reset without copying */
82             if (!isspace(*from))
83             {
84                 tokencount++;
85                 state = 1;
86                 --from;
87                 continue;
88             }
89             break;
90
91         case 3:   /* we're in a quoted human name, copy and ignore */
92             if (*from == '"')
93                 state = 1;
94             break;
95
96         case 4:   /* we're in a parenthesized human name, copy and ignore */
97             if (*from == '(')
98                 ++parendepth;
99             else if (*from == ')')
100                 --parendepth;
101             if (parendepth == 0)
102                 state = oldstate;
103             break;
104
105         case 5:   /* we're in a <>-enclosed address */
106             if (*from == '@')
107                 state = 7;
108             else if (*from == '>')
109             {
110                 INSERT_HOSTNAME
111             }
112             break;
113
114         case 6:   /* not string or comment, could be a bare address */
115             if (*from == '@')
116                 state = 7;
117
118             else if (*from == '<')
119                 state = 5;
120
121             else if (*from == '(')
122             {
123                 oldstate = 6;
124                 state = 4;
125             }
126
127             /* on proper termination with no @, insert hostname */
128             else if (*from == ',')
129             {
130                 INSERT_HOSTNAME
131                 tokencount = 0;
132             }
133
134             /* If the address token is not properly terminated, ignore it. */
135             else if (isspace(*from))
136             {
137                 const char *cp;
138
139                 /*
140                  * The only lookahead case.  If we're looking at space or tab,
141                  * we might be looking at a local name immediately followed
142                  * by a human name.
143                  */
144                 for (cp = from; isspace(*cp); cp++)
145                     continue;
146                 if (*cp == '(')
147                 {
148                     char        *bp = strchr(cp, '<');
149                     char        *ep = strchr(cp, ',');
150
151                     if (!bp || bp > ep)
152                     {
153                         INSERT_HOSTNAME
154                     }
155                 }
156             }
157
158             /* everything else, including alphanumerics, just passes through */
159             break;
160
161         case 7:   /* we're done with this address, skip to end */
162             if (*from == ',')
163             {
164                 state = 1;
165                 tokencount == 0;
166             }
167             break;
168         }
169
170         /* all characters from the old buffer get copied to the new one */
171         *buf++ = *from;
172     }
173 #undef INSERT_HOSTNAME
174
175     /* back up and nuke the appended comma sentinel */
176     *--buf = '\0';
177
178     if (nlterm)
179         strcat(buf, "\n");
180 }
181
182 char *nxtaddr(hdr)
183 /* parse addresses in succession out of a specified RFC822 header */
184 const char *hdr;        /* header to be parsed, NUL to continue previous hdr */
185 {
186     static char *tp, address[POPBUFSIZE+1];
187     static const char *hp;
188     static int  state, oldstate;
189     int parendepth;
190
191     /*
192      * Note: it is important that this routine not stop on \r, since
193      * we use \r as a marker for RFC822 continuations elsewhere.
194      */
195 #define START_HDR       0       /* before header colon */
196 #define SKIP_JUNK       1       /* skip whitespace, \n, and junk */
197 #define BARE_ADDRESS    2       /* collecting address without delimiters */
198 #define INSIDE_DQUOTE   3       /* inside double quotes */
199 #define INSIDE_PARENS   4       /* inside parentheses */
200 #define INSIDE_BRACKETS 5       /* inside bracketed address */
201 #define ENDIT_ALL       6       /* after last address */
202
203     if (hdr)
204     {
205         hp = hdr;
206         state = START_HDR;
207     }
208
209     for (; *hp; hp++)
210     {
211         switch (state)
212         {
213         case START_HDR:   /* before header colon */
214             if (*hp == '\n')
215             {
216                 state = ENDIT_ALL;
217                 return(NULL);
218             }
219             else if (*hp == ':')
220             {
221                 state = SKIP_JUNK;
222                 tp = address;
223             }
224             break;
225
226         case SKIP_JUNK:         /* looking for address start */
227             if (*hp == '\n')            /* no more addresses */
228             {
229                 state = ENDIT_ALL;
230                 return(NULL);
231             }
232             else if (*hp == '\\')       /* handle RFC822 escaping */
233             {
234                 *tp++ = *hp++;                  /* take the escape */
235                 *tp++ = *hp;                    /* take following char */
236             }
237             else if (*hp == '"')        /* quoted string */
238             {
239                 oldstate = SKIP_JUNK;
240                 state = INSIDE_DQUOTE;
241                 *tp++ = *hp;
242             }
243             else if (*hp == '(')        /* address comment -- ignore */
244             {
245                 parendepth = 1;
246                 state = INSIDE_PARENS;    
247             }
248             else if (*hp == '<')        /* begin <address> */
249             {
250                 state = INSIDE_BRACKETS;
251                 tp = address;
252             }
253             else if (!isspace(*hp))     /* ignore space */
254             {
255                 --hp;
256                 state = BARE_ADDRESS;
257             }
258             break;
259
260         case BARE_ADDRESS:      /* collecting address without delimiters */
261             if (*hp == '\n')            /* end of bare address */
262             {
263                 if (tp > address)
264                 {
265                     *tp++ = '\0';
266                     state = ENDIT_ALL;
267                     return(tp = address);
268                 }
269             }
270             else if (*hp == '\\')       /* handle RFC822 escaping */
271             {
272                 *tp++ = *hp++;                  /* take the escape */
273                 *tp++ = *hp;                    /* take following char */
274             }
275             else if (*hp == ',')        /* end of address */
276             {
277                 if (tp > address)
278                 {
279                     *tp++ = '\0';
280                     state = SKIP_JUNK;
281                     return(tp = address);
282                 }
283             }
284             else if (*hp == '<')        /* beginning of real address */
285             {
286                 state = INSIDE_BRACKETS;
287                 tp = address;
288             }
289             else                /* just take it */
290                 *tp++ = *hp;
291             break;
292
293         case INSIDE_DQUOTE:     /* we're in a quoted string, copy verbatim */
294             if (*hp == '\n')            /* premature end of string */
295             {
296                 state = ENDIT_ALL;
297                 return(NULL);
298             }
299             else if (*hp == '\\')       /* handle RFC822 escaping */
300             {
301                 *tp++ = *hp++;                  /* take the escape */
302                 *tp++ = *hp;                    /* take following char */
303             }
304             else if (*hp != '"')
305                 *tp++ = *hp;
306             else
307             {
308                 *tp++ = *hp;
309                 state = oldstate;
310             }
311             break;
312
313         case INSIDE_PARENS:     /* we're in a parenthesized comment, ignore */
314             if (*hp == '\n')            /* end of line, just bomb out */
315                 return(NULL);
316             else if (*hp == '\\')       /* handle RFC822 escaping */
317             {
318                 *tp++ = *hp++;                  /* take the escape */
319                 *tp++ = *hp;                    /* take following char */
320             }
321             else if (*hp == '(')
322                 ++parendepth;
323             else if (*hp == ')')
324                 --parendepth;
325             if (parendepth == 0)
326                 state = SKIP_JUNK;
327             break;
328
329         case INSIDE_BRACKETS:   /* possible <>-enclosed address */
330             if (*hp == '\\')            /* handle RFC822 escaping */
331             {
332                 *tp++ = *hp++;                  /* take the escape */
333                 *tp++ = *hp;                    /* take following char */
334             }
335             else if (*hp == '>')        /* end of address */
336             {
337                 *tp++ = '\0';
338                 state = SKIP_JUNK;
339                 ++hp;
340                 return(tp = address);
341             }
342             else if (*hp == '<')        /* nested <> */
343                 tp = address;
344             else if (*hp == '"')        /* quoted address */
345             {
346                 *tp++ = *hp;
347                 oldstate = INSIDE_BRACKETS;
348                 state = INSIDE_DQUOTE;
349             }
350             else                        /* just copy address */
351                 *tp++ = *hp;
352             break;
353
354         case ENDIT_ALL:         /* after last address */
355             return(NULL);
356             break;
357         }
358     }
359
360     return(NULL);
361 }
362
363 #ifdef TESTMAIN
364 main(int argc, char *argv[])
365 {
366     char        buf[POPBUFSIZE], *cp;
367     int         reply =  (argc > 1 && !strcmp(argv[1], "-r"));
368
369     while (fgets(buf, sizeof(buf)-1, stdin))
370     {
371         if (strncmp("From: ", buf, 6)
372                     && strncmp("To: ", buf, 4)
373                     && strncmp("Reply-", buf, 6)
374                     && strncmp("Cc: ", buf, 4)
375                     && strncmp("Bcc: ", buf, 5))
376             continue;
377         else
378         {
379             fputs(buf, stdout);
380             if (reply)
381             {
382                 reply_hack(buf, "HOSTNAME.NET");
383                 printf("Rewritten buffer: %s", buf);
384             }
385             else
386                 if ((cp = nxtaddr(buf)) != (char *)NULL)
387                     do {
388                         printf("\t%s\n", cp);
389                     } while
390                         ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
391         }
392
393     }
394 }
395 #endif /* TESTMAIN */
396
397 /* rfc822.c end */