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