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