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