]> Pileus Git - ~andy/fetchmail/blob - rfc822.c
efdbcfd8cdf3c511dd27a0980b24f88b9e33f313
[~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 #define HEADER_END(p)   ((p)[0] == '\n' && ((p)[1] != ' ' && (p)[1] != '\t'))
19
20 void reply_hack(buf, host)
21 /* hack message headers so replies will work properly */
22 char *buf;              /* header to be hacked */
23 const char *host;       /* server hostname */
24 {
25     const char *from;
26     int parendepth, state, has_host_part;
27     char mycopy[MSGBUFSIZE+1];
28
29     if (strncmp("From: ", buf, 6)
30         && strncmp("To: ", buf, 4)
31         && strncmp("Reply-", buf, 6)
32         && strncmp("Cc: ", buf, 4)
33         && strncmp("Bcc: ", buf, 5)) {
34         return;
35     }
36
37     strcpy(mycopy, buf);
38     parendepth = state = 0;
39     has_host_part = FALSE;
40     for (from = mycopy; *from; from++)
41     {
42 #ifdef TESTMAIN
43         printf("state %d: %s", state, mycopy);
44         printf("%*s^\n", from - mycopy + 10, " ");
45 #endif /* TESTMAIN */
46         if (*from == '(')
47             ++parendepth;
48         else if (*from == ')')
49             --parendepth;
50
51         if (!parendepth && !has_host_part)
52             switch (state)
53             {
54             case 0:   /* before header colon */
55                 if (*from == ':')
56                     state = 1;
57                 break;
58
59             case 1:   /* we've seen the colon, we're looking for addresses */
60                 if (*from == '<')
61                     state = 2;
62                 else if (*from == '@')
63                     has_host_part = TRUE;
64                 else if ((*from == ',' || HEADER_END(from)) && !has_host_part)
65                 {
66                     while (isspace(*from))
67                         --from;
68                     from++;
69                     while (isspace(*buf))
70                         --buf;
71                     buf++;
72                     strcpy(buf, "@");
73                     strcat(buf, host);
74                     buf += strlen(buf);
75                     has_host_part = TRUE;
76                 }
77                 break;
78
79             case 2:   /* we're in a <>-enclosed address */
80                 if (*from == '@')
81                     has_host_part = TRUE;
82                 else if (*from == '>' && !has_host_part)
83                 {
84                     strcpy(buf, "@");
85                     strcat(buf, host);
86                     buf += strlen(buf);
87                     has_host_part = TRUE;
88                 }
89                 break;
90             }
91
92         /* all characters from the old buffer get copied to the new one */
93         *buf++ = *from;
94     }
95
96     *buf = '\0';
97 }
98
99 char *nxtaddr(hdr)
100 /* parse addresses in succession out of a specified RFC822 header */
101 const char *hdr;        /* header to be parsed, NUL to continue previous hdr */
102 {
103     static char *tp, address[POPBUFSIZE+1];
104     static const char *hp;
105     static int  state, oldstate;
106     int parendepth;
107
108 #define START_HDR       0       /* before header colon */
109 #define SKIP_JUNK       1       /* skip whitespace, \n, and junk */
110 #define BARE_ADDRESS    2       /* collecting address without delimiters */
111 #define INSIDE_DQUOTE   3       /* inside double quotes */
112 #define INSIDE_PARENS   4       /* inside parentheses */
113 #define INSIDE_BRACKETS 5       /* inside bracketed address */
114 #define ENDIT_ALL       6       /* after last address */
115
116     if (hdr)
117     {
118         hp = hdr;
119         state = START_HDR;
120     }
121
122     for (; *hp; hp++)
123     {
124         switch (state)
125         {
126         case START_HDR:   /* before header colon */
127             if (HEADER_END(hp))
128             {
129                 state = ENDIT_ALL;
130                 return(NULL);
131             }
132             else if (*hp == ':')
133             {
134                 state = SKIP_JUNK;
135                 tp = address;
136             }
137             break;
138
139         case SKIP_JUNK:         /* looking for address start */
140             if (HEADER_END(hp))         /* no more addresses */
141             {
142                 state = ENDIT_ALL;
143                 return(NULL);
144             }
145             else if (*hp == '\\')       /* handle RFC822 escaping */
146             {
147                 *tp++ = *hp++;                  /* take the escape */
148                 *tp++ = *hp;                    /* take following char */
149             }
150             else if (*hp == '"')        /* quoted string */
151             {
152                 oldstate = SKIP_JUNK;
153                 state = INSIDE_DQUOTE;
154                 *tp++ = *hp;
155             }
156             else if (*hp == '(')        /* address comment -- ignore */
157             {
158                 parendepth = 1;
159                 state = INSIDE_PARENS;    
160             }
161             else if (*hp == '<')        /* begin <address> */
162             {
163                 state = INSIDE_BRACKETS;
164                 tp = address;
165             }
166             else if (!isspace(*hp))     /* ignore space */
167             {
168                 --hp;
169                 state = BARE_ADDRESS;
170             }
171             break;
172
173         case BARE_ADDRESS:      /* collecting address without delimiters */
174             if (HEADER_END(hp))         /* end of bare address */
175             {
176                 if (tp > address)
177                 {
178                     while (isspace(*--tp))
179                         continue;
180                     *++tp = '\0';
181                     state = ENDIT_ALL;
182                     return(tp = address);
183                 }
184             }
185             else if (*hp == '\\')       /* handle RFC822 escaping */
186             {
187                 *tp++ = *hp++;                  /* take the escape */
188                 *tp++ = *hp;                    /* take following char */
189             }
190             else if (*hp == ',')        /* end of address */
191             {
192                 if (tp > address)
193                 {
194                     *tp++ = '\0';
195                     state = SKIP_JUNK;
196                     return(tp = address);
197                 }
198             }
199             else if (*hp == '<')        /* beginning of real address */
200             {
201                 state = INSIDE_BRACKETS;
202                 tp = address;
203             }
204             else                /* just take it */
205                 *tp++ = *hp;
206             break;
207
208         case INSIDE_DQUOTE:     /* we're in a quoted string, copy verbatim */
209             if (HEADER_END(hp))         /* premature end of string */
210             {
211                 state = ENDIT_ALL;
212                 return(NULL);
213             }
214             else if (*hp == '\\')       /* handle RFC822 escaping */
215             {
216                 *tp++ = *hp++;                  /* take the escape */
217                 *tp++ = *hp;                    /* take following char */
218             }
219             else if (*hp != '"')
220                 *tp++ = *hp;
221             else
222             {
223                 *tp++ = *hp;
224                 state = oldstate;
225             }
226             break;
227
228         case INSIDE_PARENS:     /* we're in a parenthesized comment, ignore */
229             if (HEADER_END(hp))         /* end of line, just bomb out */
230             {
231                 state = ENDIT_ALL;
232                 return(NULL);
233             }
234             else if (*hp == '\\')       /* handle RFC822 escaping */
235             {
236                 *tp++ = *hp++;                  /* take the escape */
237                 *tp++ = *hp;                    /* take following char */
238             }
239             else if (*hp == '(')
240                 ++parendepth;
241             else if (*hp == ')')
242                 --parendepth;
243             if (parendepth == 0)
244                 state = SKIP_JUNK;
245             break;
246
247         case INSIDE_BRACKETS:   /* possible <>-enclosed address */
248             if (HEADER_END(hp))         /* end of line, just bomb out */
249             {
250                 state = ENDIT_ALL;
251                 return(NULL);
252             }
253             else if (*hp == '\\')       /* handle RFC822 escaping */
254             {
255                 *tp++ = *hp++;                  /* take the escape */
256                 *tp++ = *hp;                    /* take following char */
257             }
258             else if (*hp == '>')        /* end of address */
259             {
260                 *tp++ = '\0';
261                 state = SKIP_JUNK;
262                 ++hp;
263                 return(tp = address);
264             }
265             else if (*hp == '<')        /* nested <> */
266                 tp = address;
267             else if (*hp == '"')        /* quoted address */
268             {
269                 *tp++ = *hp;
270                 oldstate = INSIDE_BRACKETS;
271                 state = INSIDE_DQUOTE;
272             }
273             else                        /* just copy address */
274                 *tp++ = *hp;
275             break;
276
277         case ENDIT_ALL:         /* after last address */
278             return(NULL);
279             break;
280         }
281     }
282
283     return(NULL);
284 }
285
286 #ifdef TESTMAIN
287 main(int argc, char *argv[])
288 {
289     char        buf[MSGBUFSIZE], *cp;
290     int         reply =  (argc > 1 && !strcmp(argv[1], "-r"));
291
292     while (fgets(buf, sizeof(buf)-1, stdin))
293     {
294         if (strncmp("From: ", buf, 6)
295                     && strncmp("To: ", buf, 4)
296                     && strncmp("Reply-", buf, 6)
297                     && strncmp("Cc: ", buf, 4)
298                     && strncmp("Bcc: ", buf, 5))
299             continue;
300         else
301         {
302             fputs(buf, stdout);
303             if (reply)
304             {
305                 reply_hack(buf, "HOSTNAME.NET");
306                 printf("Rewritten buffer: %s", buf);
307             }
308             else
309                 if ((cp = nxtaddr(buf)) != (char *)NULL)
310                     do {
311                         printf("\t%s\n", cp);
312                     } while
313                         ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
314         }
315
316     }
317 }
318 #endif /* TESTMAIN */
319
320 /* rfc822.c end */