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