Added TAGS to .cvsignore
[claws.git] / src / 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  * Modified by Hiroyuki Yamamoto <hiro-y@kcn.ne.jp>
8  */
9
10 #include  <stdio.h>
11 #include  <ctype.h>
12 #include  <string.h>
13 #include  <stdlib.h>
14 #include  <glib.h>
15 #include  "rfc822.h"
16
17 /* output noise level */
18 #define         O_SILENT        0       /* mute, max squelch, etc. */
19 #define         O_NORMAL        1       /* user-friendly */
20 #define         O_VERBOSE       2       /* chatty */
21 #define         O_DEBUG         3       /* prolix */
22 #define         O_MONITOR       O_VERBOSE
23
24 static int outlevel = O_SILENT;
25
26 #define         POPBUFSIZE      512     /* max length of response (RFC1939) */
27
28 #define HEADER_END(p)   ((p)[0] == '\n' && ((p)[1] != ' ' && (p)[1] != '\t'))
29
30 #ifdef TESTMAIN
31 static int verbose;
32 char *program_name = "rfc822";
33 #endif /* TESTMAIN */
34
35 char *reply_hack(buf, host)
36 /* hack message headers so replies will work properly */
37 char *buf;              /* header to be hacked */
38 const char *host;       /* server hostname */
39 {
40     char *from, *cp, last_nws = '\0', *parens_from = NULL;
41     int parendepth, state, has_bare_name_part, has_host_part;
42 #ifndef TESTMAIN
43     int addresscount = 1;
44 #endif /* TESTMAIN */
45
46     if (strncasecmp("From: ", buf, 6)
47         && strncasecmp("To: ", buf, 4)
48         && strncasecmp("Reply-To: ", buf, 10)
49         && strncasecmp("Return-Path: ", buf, 13)
50         && strncasecmp("Cc: ", buf, 4)
51         && strncasecmp("Bcc: ", buf, 5)
52         && strncasecmp("Resent-From: ", buf, 13)
53         && strncasecmp("Resent-To: ", buf, 11)
54         && strncasecmp("Resent-Cc: ", buf, 11)
55         && strncasecmp("Resent-Bcc: ", buf, 12)
56         && strncasecmp("Apparently-From:", buf, 16)
57         && strncasecmp("Apparently-To:", buf, 14)
58         && strncasecmp("Sender:", buf, 7)
59         && strncasecmp("Resent-Sender:", buf, 14)
60        ) {
61         return(buf);
62     }
63
64 #ifndef TESTMAIN
65     if (outlevel >= O_DEBUG)
66         fprintf(stdout, "About to rewrite %s", buf);
67
68     /* make room to hack the address; buf must be malloced */
69     for (cp = buf; *cp; cp++)
70         if (*cp == ',' || isspace(*cp))
71             addresscount++;
72     buf = (char *)g_realloc(buf, strlen(buf) + addresscount * strlen(host) + 1);
73 #endif /* TESTMAIN */
74
75     /*
76      * This is going to foo up on some ill-formed addresses.
77      * Note that we don't rewrite the fake address <> in order to
78      * avoid screwing up bounce suppression with a null Return-Path.
79      */
80
81     parendepth = state = 0;
82     has_host_part = has_bare_name_part = FALSE;
83     for (from = buf; *from; from++)
84     {
85 #ifdef TESTMAIN
86         if (verbose)
87         {
88             printf("state %d: %s", state, buf);
89             printf("%*s^\n", from - buf + 10, " ");
90         }
91 #endif /* TESTMAIN */
92         if (state != 2) {
93             if (*from == '(')
94                 ++parendepth;
95             else if (*from == ')')
96                 --parendepth;
97         }
98
99         if (!parendepth && !has_host_part)
100             switch (state)
101             {
102             case 0:     /* before header colon */
103                 if (*from == ':')
104                     state = 1;
105                 break;
106
107             case 1:     /* we've seen the colon, we're looking for addresses */
108                 if (!isspace(*from))
109                     last_nws = *from;
110                 if (*from == '<')
111                     state = 3;
112                 else if (*from == '@')
113                     has_host_part = TRUE;
114                 else if (*from == '"')
115                     state = 2;
116                 /*
117                  * Not expanding on last non-WS == ';' deals with groupnames,
118                  * an obscure misfeature described in sections
119                  * 6.1, 6.2.6, and A.1.5 of the RFC822 standard.
120                  */
121                 else if ((*from == ',' || HEADER_END(from))
122                          && has_bare_name_part
123                          && !has_host_part
124                          && last_nws != ';')
125                 {
126                     int hostlen;
127                     char *p;
128
129                     p = from;
130                     if (parens_from)
131                         from = parens_from;
132                     while (isspace(*from) || (*from == ','))
133                         --from;
134                     from++;
135                     hostlen = strlen(host);
136                     for (cp = from + strlen(from); cp >= from; --cp)
137                         cp[hostlen+1] = *cp;
138                     *from++ = '@';
139                     memcpy(from, host, hostlen);
140                     from = p + hostlen + 1;
141                     has_host_part = TRUE;
142                 } 
143                 else if (from[1] == '('
144                          && has_bare_name_part
145                          && !has_host_part
146                          && last_nws != ';' && last_nws != ')')
147                 {
148                     parens_from = from;
149                 } 
150                 else if (!isspace(*from))
151                     has_bare_name_part = TRUE;
152                 break;
153
154             case 2:     /* we're in a string */
155                 if (*from == '"')
156                     state = 1;
157                 break;
158
159             case 3:     /* we're in a <>-enclosed address */
160                 if (*from == '@')
161                     has_host_part = TRUE;
162                 else if (*from == '>' && from[-1] != '<')
163                 {
164                     state = 1;
165                     if (!has_host_part)
166                     {
167                         int hostlen;
168
169                         hostlen = strlen(host);
170                         for (cp = from + strlen(from); cp >= from; --cp)
171                             cp[hostlen+1] = *cp;
172                         *from++ = '@';
173                         memcpy(from, host, hostlen);
174                         from += hostlen;
175                         has_host_part = TRUE;
176                     }
177                 }
178                 break;
179             }
180
181         /*
182          * If we passed a comma, reset everything.
183          */
184         if (from[-1] == ',' && !parendepth) {
185           has_host_part = has_bare_name_part = FALSE;
186           parens_from = NULL;
187         }
188     }
189
190 #ifndef TESTMAIN
191     if (outlevel >= O_DEBUG)
192         fprintf(stdout, "Rewritten version is %s\n", buf);
193 #endif /* TESTMAIN */
194     return(buf);
195 }
196
197 char *nxtaddr(hdr)
198 /* parse addresses in succession out of a specified RFC822 header */
199 const char *hdr;        /* header to be parsed, NUL to continue previous hdr */
200 {
201     static char *tp, address[POPBUFSIZE+1];
202     static const char *hp;
203     static int  state, oldstate;
204 #ifdef TESTMAIN
205     static const char *orighdr;
206 #endif /* TESTMAIN */
207     int parendepth = 0;
208
209 #define START_HDR       0       /* before header colon */
210 #define SKIP_JUNK       1       /* skip whitespace, \n, and junk */
211 #define BARE_ADDRESS    2       /* collecting address without delimiters */
212 #define INSIDE_DQUOTE   3       /* inside double quotes */
213 #define INSIDE_PARENS   4       /* inside parentheses */
214 #define INSIDE_BRACKETS 5       /* inside bracketed address */
215 #define ENDIT_ALL       6       /* after last address */
216
217     if (hdr)
218     {
219         hp = hdr;
220         state = START_HDR;
221 #ifdef TESTMAIN
222         orighdr = hdr;
223 #endif /* TESTMAIN */
224         tp = address;
225     }
226
227     for (; *hp; hp++)
228     {
229 #ifdef TESTMAIN
230         if (verbose)
231         {
232             printf("state %d: %s", state, orighdr);
233             printf("%*s^\n", hp - orighdr + 10, " ");
234         }
235 #endif /* TESTMAIN */
236
237         if (state == ENDIT_ALL)         /* after last address */
238             return(NULL);
239         else if (HEADER_END(hp))
240         {
241             state = ENDIT_ALL;
242             if (tp > address)
243             {
244                 while (isspace(*--tp))
245                     continue;
246                 *++tp = '\0';
247             }
248             return(tp > address ? (tp = address) : (char *)NULL);
249         }
250         else if (*hp == '\\')           /* handle RFC822 escaping */
251         {
252             if (state != INSIDE_PARENS)
253             {
254                 *tp++ = *hp++;                  /* take the escape */
255                 *tp++ = *hp;                    /* take following char */
256             }
257         }
258         else switch (state)
259         {
260         case START_HDR:   /* before header colon */
261             if (*hp == ':')
262                 state = SKIP_JUNK;
263             break;
264
265         case SKIP_JUNK:         /* looking for address start */
266             if (*hp == '"')     /* quoted string */
267             {
268                 oldstate = SKIP_JUNK;
269                 state = INSIDE_DQUOTE;
270                 *tp++ = *hp;
271             }
272             else if (*hp == '(')        /* address comment -- ignore */
273             {
274                 parendepth = 1;
275                 oldstate = SKIP_JUNK;
276                 state = INSIDE_PARENS;    
277             }
278             else if (*hp == '<')        /* begin <address> */
279             {
280                 state = INSIDE_BRACKETS;
281                 tp = address;
282             }
283             else if (*hp != ',' && !isspace(*hp))
284             {
285                 --hp;
286                 state = BARE_ADDRESS;
287             }
288             break;
289
290         case BARE_ADDRESS:      /* collecting address without delimiters */
291             if (*hp == ',')     /* end of address */
292             {
293                 if (tp > address)
294                 {
295                     *tp++ = '\0';
296                     state = SKIP_JUNK;
297                     return(tp = address);
298                 }
299             }
300             else if (*hp == '(')        /* beginning of comment */
301             {
302                 parendepth = 1;
303                 oldstate = BARE_ADDRESS;
304                 state = INSIDE_PARENS;    
305             }
306             else if (*hp == '<')        /* beginning of real address */
307             {
308                 state = INSIDE_BRACKETS;
309                 tp = address;
310             }
311             else if (!isspace(*hp))     /* just take it, ignoring whitespace */
312                 *tp++ = *hp;
313             break;
314
315         case INSIDE_DQUOTE:     /* we're in a quoted string, copy verbatim */
316             if (*hp != '"')
317                 *tp++ = *hp;
318             else
319             {
320                 *tp++ = *hp;
321                 state = oldstate;
322             }
323             break;
324
325         case INSIDE_PARENS:     /* we're in a parenthesized comment, ignore */
326             if (*hp == '(')
327                 ++parendepth;
328             else if (*hp == ')')
329                 --parendepth;
330             if (parendepth == 0)
331                 state = oldstate;
332             break;
333
334         case INSIDE_BRACKETS:   /* possible <>-enclosed address */
335             if (*hp == '>')     /* end of address */
336             {
337                 *tp++ = '\0';
338                 state = SKIP_JUNK;
339                 ++hp;
340                 return(tp = address);
341             }
342             else if (*hp == '<')        /* nested <> */
343                 tp = address;
344             else if (*hp == '"')        /* quoted address */
345             {
346                 *tp++ = *hp;
347                 oldstate = INSIDE_BRACKETS;
348                 state = INSIDE_DQUOTE;
349             }
350             else                        /* just copy address */
351                 *tp++ = *hp;
352             break;
353         }
354     }
355
356     return(NULL);
357 }
358
359 #ifdef TESTMAIN
360 static void parsebuf(char *longbuf, int reply)
361 {
362     char        *cp;
363
364     if (reply)
365     {
366         reply_hack(longbuf, "HOSTNAME.NET");
367         printf("Rewritten buffer: %s", longbuf);
368     }
369     else
370         if ((cp = nxtaddr(longbuf)) != (char *)NULL)
371             do {
372                 printf("\t-> \"%s\"\n", cp);
373             } while
374                 ((cp = nxtaddr((char *)NULL)) != (char *)NULL);
375 }
376
377
378
379 main(int argc, char *argv[])
380 {
381     char        buf[MSGBUFSIZE], longbuf[BUFSIZ];
382     int         ch, reply;
383     
384     verbose = reply = FALSE;
385     while ((ch = getopt(argc, argv, "rv")) != EOF)
386         switch(ch)
387         {
388         case 'r':
389             reply = TRUE;
390             break;
391
392         case 'v':
393             verbose = TRUE;
394             break;
395         }
396
397     while (fgets(buf, sizeof(buf)-1, stdin))
398     {
399         if (buf[0] == ' ' || buf[0] == '\t')
400             strcat(longbuf, buf);
401         else if (!strncasecmp("From: ", buf, 6)
402                     || !strncasecmp("To: ", buf, 4)
403                     || !strncasecmp("Reply-", buf, 6)
404                     || !strncasecmp("Cc: ", buf, 4)
405                     || !strncasecmp("Bcc: ", buf, 5))
406             strcpy(longbuf, buf);       
407         else if (longbuf[0])
408         {
409             if (verbose)
410                 fputs(longbuf, stdout);
411             parsebuf(longbuf, reply);
412             longbuf[0] = '\0';
413         }
414     }
415     if (longbuf[0])
416     {
417         if (verbose)
418             fputs(longbuf, stdout);
419         parsebuf(longbuf, reply);
420     }
421 }
422 #endif /* TESTMAIN */
423
424 /* rfc822.c end */