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