]> Pileus Git - ~andy/fetchmail/blob - rfc822.c
Fix typo in header name.
[~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 char *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, last_nws = '\0';
29     int parendepth, state, has_bare_name_part, has_host_part;
30     int addresscount = 1;
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         && strncasecmp("Resent-From: ", buf, 13)
39         && strncasecmp("Resent-To: ", buf, 11)
40         && strncasecmp("Resent-Cc: ", buf, 11)
41         && strncasecmp("Resent-Bcc: ", buf, 12)
42         && strncasecmp("Apparently-From:", buf, 16)
43         && strncasecmp("Apparently-To:", buf, 14)
44         && strncasecmp("Sender:", buf, 7)
45         && strncasecmp("Resent-Sender:", buf, 14)
46        ) {
47         return(buf);
48     }
49
50 #ifndef TESTMAIN
51     /* make room to hack the address; buf must be malloced */
52     for (cp = buf; *cp; cp++)
53         if (*cp == ',' || isspace(*cp))
54             addresscount++;
55     buf = (char *)xrealloc(buf, strlen(buf) + addresscount * strlen(host) + 1);
56 #endif /* TESTMAIN */
57
58     /*
59      * This is going to foo up on some ill-formed addresses.  For example,
60      * "From: John Smith (Systems) <jsmith@domain>" will get rewritten as 
61      * "From: John Smith@my.pop.server (Systems) <jsmith@domain>" because
62      * the state machine can't look ahead to the <> part past the comment
63      * and instead treats `John Smith' as a bareword address.
64      */
65
66     parendepth = state = 0;
67     has_host_part = has_bare_name_part = FALSE;
68     for (from = buf; *from; from++)
69     {
70 #ifdef TESTMAIN
71         if (verbose)
72         {
73             printf("state %d: %s", state, buf);
74             printf("%*s^\n", from - buf + 10, " ");
75         }
76 #endif /* TESTMAIN */
77         if (state != 2)
78             if (*from == '(')
79                 ++parendepth;
80             else if (*from == ')')
81                 --parendepth;
82
83         if (!parendepth && !has_host_part)
84             switch (state)
85             {
86             case 0:     /* before header colon */
87                 if (*from == ':')
88                     state = 1;
89                 break;
90
91             case 1:     /* we've seen the colon, we're looking for addresses */
92                 if (!isspace(*from))
93                     last_nws = *from;
94                 if (*from == '<')
95                     state = 3;
96                 else if (*from == '@')
97                     has_host_part = TRUE;
98                 else if (*from == '"')
99                     state = 2;
100                 /*
101                  * Not expanding on last non-WS == ';' deals with groupnames,
102                  * an obscure misfeature described in sections
103                  * 6.1, 6.2.6, and A.1.5 of the RFC822 standard.
104                  */
105                 else if ((*from == ',' || HEADER_END(from) || from[1] == '(')
106                          && has_bare_name_part
107                          && !has_host_part
108                          && last_nws != ';' && last_nws != ')')
109                 {
110                     int hostlen;
111
112                     while (isspace(*from) || (*from == ','))
113                         --from;
114                     from++;
115                     hostlen = strlen(host);
116                     for (cp = from + strlen(from); cp >= from; --cp)
117                         cp[hostlen+1] = *cp;
118                     *from++ = '@';
119                     memcpy(from, host, hostlen);
120                     from += hostlen;
121                     has_host_part = TRUE;
122                 } 
123                 else if (!isspace(*from))
124                     has_bare_name_part = TRUE;
125                 break;
126
127             case 2:     /* we're in a string */
128                 if (*from == '"')
129                     state = 1;
130                 break;
131
132             case 3:     /* we're in a <>-enclosed address */
133                 if (*from == '@')
134                     has_host_part = TRUE;
135                 else if (*from == '>')
136                 {
137                     state = 1;
138                     if (!has_host_part)
139                     {
140                         int hostlen;
141
142                         hostlen = strlen(host);
143                         for (cp = from + strlen(from); cp >= from; --cp)
144                             cp[hostlen+1] = *cp;
145                         *from++ = '@';
146                         memcpy(from, host, hostlen);
147                         from += hostlen;
148                         has_host_part = TRUE;
149                     }
150                 }
151                 break;
152             }
153
154         /*
155          * If we passed a comma, reset everything.
156          */
157         if (from[-1] == ',' && !parendepth) {
158           has_host_part = has_bare_name_part = FALSE;
159         }
160     }
161
162     return(buf);
163 }
164
165 char *nxtaddr(hdr)
166 /* parse addresses in succession out of a specified RFC822 header */
167 const char *hdr;        /* header to be parsed, NUL to continue previous hdr */
168 {
169     static char *tp, address[POPBUFSIZE+1];
170     static const char *hp;
171     static int  state, oldstate;
172 #ifdef TESTMAIN
173     static const char *orighdr;
174 #endif /* TESTMAIN */
175     int parendepth = 0;
176
177 #define START_HDR       0       /* before header colon */
178 #define SKIP_JUNK       1       /* skip whitespace, \n, and junk */
179 #define BARE_ADDRESS    2       /* collecting address without delimiters */
180 #define INSIDE_DQUOTE   3       /* inside double quotes */
181 #define INSIDE_PARENS   4       /* inside parentheses */
182 #define INSIDE_BRACKETS 5       /* inside bracketed address */
183 #define ENDIT_ALL       6       /* after last address */
184
185     if (hdr)
186     {
187         hp = hdr;
188         state = START_HDR;
189 #ifdef TESTMAIN
190         orighdr = hdr;
191 #endif /* TESTMAIN */
192         tp = address;
193     }
194
195     for (; *hp; hp++)
196     {
197 #ifdef TESTMAIN
198         if (verbose)
199         {
200             printf("state %d: %s", state, orighdr);
201             printf("%*s^\n", hp - orighdr + 10, " ");
202         }
203 #endif /* TESTMAIN */
204
205         if (state == ENDIT_ALL)         /* after last address */
206             return(NULL);
207         else if (HEADER_END(hp))
208         {
209             state = ENDIT_ALL;
210             if (tp > address)
211             {
212                 while (isspace(*--tp))
213                     continue;
214                 *++tp = '\0';
215             }
216             return(tp > address ? (tp = address) : (char *)NULL);
217         }
218         else if (*hp == '\\')           /* handle RFC822 escaping */
219         {
220             if (state != INSIDE_PARENS)
221             {
222                 *tp++ = *hp++;                  /* take the escape */
223                 *tp++ = *hp;                    /* take following char */
224             }
225         }
226         else switch (state)
227         {
228         case START_HDR:   /* before header colon */
229             if (*hp == ':')
230                 state = SKIP_JUNK;
231             break;
232
233         case SKIP_JUNK:         /* looking for address start */
234             if (*hp == '"')     /* quoted string */
235             {
236                 oldstate = SKIP_JUNK;
237                 state = INSIDE_DQUOTE;
238                 *tp++ = *hp;
239             }
240             else if (*hp == '(')        /* address comment -- ignore */
241             {
242                 parendepth = 1;
243                 oldstate = SKIP_JUNK;
244                 state = INSIDE_PARENS;    
245             }
246             else if (*hp == '<')        /* begin <address> */
247             {
248                 state = INSIDE_BRACKETS;
249                 tp = address;
250             }
251             else if (*hp != ',' && !isspace(*hp))
252             {
253                 --hp;
254                 state = BARE_ADDRESS;
255             }
256             break;
257
258         case BARE_ADDRESS:      /* collecting address without delimiters */
259             if (*hp == ',')     /* end of address */
260             {
261                 if (tp > address)
262                 {
263                     *tp++ = '\0';
264                     state = SKIP_JUNK;
265                     return(tp = address);
266                 }
267             }
268             else if (*hp == '(')        /* beginning of comment */
269             {
270                 parendepth = 1;
271                 oldstate = BARE_ADDRESS;
272                 state = INSIDE_PARENS;    
273             }
274             else if (*hp == '<')        /* beginning of real address */
275             {
276                 state = INSIDE_BRACKETS;
277                 tp = address;
278             }
279             else if (!isspace(*hp))     /* just take it, ignoring whitespace */
280                 *tp++ = *hp;
281             break;
282
283         case INSIDE_DQUOTE:     /* we're in a quoted string, copy verbatim */
284             if (*hp != '"')
285                 *tp++ = *hp;
286             else
287             {
288                 *tp++ = *hp;
289                 state = oldstate;
290             }
291             break;
292
293         case INSIDE_PARENS:     /* we're in a parenthesized comment, ignore */
294             if (*hp == '(')
295                 ++parendepth;
296             else if (*hp == ')')
297                 --parendepth;
298             if (parendepth == 0)
299                 state = oldstate;
300             break;
301
302         case INSIDE_BRACKETS:   /* possible <>-enclosed address */
303             if (*hp == '>')     /* end of address */
304             {
305                 *tp++ = '\0';
306                 state = SKIP_JUNK;
307                 ++hp;
308                 return(tp = address);
309             }
310             else if (*hp == '<')        /* nested <> */
311                 tp = address;
312             else if (*hp == '"')        /* quoted address */
313             {
314                 *tp++ = *hp;
315                 oldstate = INSIDE_BRACKETS;
316                 state = INSIDE_DQUOTE;
317             }
318             else                        /* just copy address */
319                 *tp++ = *hp;
320             break;
321         }
322     }
323
324     return(NULL);
325 }
326
327 #ifdef TESTMAIN
328 static void parsebuf(char *longbuf, int reply)
329 {
330     char        *cp;
331
332     if (reply)
333     {
334         reply_hack(longbuf, "HOSTNAME.NET");
335         printf("Rewritten buffer: %s", longbuf);
336     }
337     else
338         if ((cp = nxtaddr(longbuf)) != (char *)NULL)
339             do {
340                 printf("\t-> \"%s\"\n", cp);
341             } while
342                 ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
343 }
344
345
346
347 main(int argc, char *argv[])
348 {
349     char        buf[MSGBUFSIZE], longbuf[BUFSIZ];
350     int         ch, reply;
351     
352     verbose = reply = FALSE;
353     while ((ch = getopt(argc, argv, "rv")) != EOF)
354         switch(ch)
355         {
356         case 'r':
357             reply = TRUE;
358             break;
359
360         case 'v':
361             verbose = TRUE;
362             break;
363         }
364
365     while (fgets(buf, sizeof(buf)-1, stdin))
366     {
367         if (buf[0] == ' ' || buf[0] == '\t')
368             strcat(longbuf, buf);
369         else if (!strncasecmp("From: ", buf, 6)
370                     || !strncasecmp("To: ", buf, 4)
371                     || !strncasecmp("Reply-", buf, 6)
372                     || !strncasecmp("Cc: ", buf, 4)
373                     || !strncasecmp("Bcc: ", buf, 5))
374             strcpy(longbuf, buf);       
375         else if (longbuf[0])
376         {
377             if (verbose)
378                 fputs(longbuf, stdout);
379             parsebuf(longbuf, reply);
380             longbuf[0] = '\0';
381         }
382     }
383     if (longbuf[0])
384     {
385         if (verbose)
386             fputs(longbuf, stdout);
387         parsebuf(longbuf, reply);
388     }
389 }
390 #endif /* TESTMAIN */
391
392 /* rfc822.c end */