initial message templates support
[claws.git] / src / rfc822.c
blob766029ac2f2042d202b63a13472a094175cc395d
1 /*
2 * rfc822.c -- code for slicing and dicing RFC822 mail headers
4 * Copyright 1997 by Eric S. Raymond
5 * For license terms, see the file COPYING in this directory.
7 * Modified by Hiroyuki Yamamoto <hiro-y@kcn.ne.jp>
8 */
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <string.h>
13 #include <stdlib.h>
14 #include <glib.h>
15 #include "rfc822.h"
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
24 static int outlevel = O_SILENT;
26 #define POPBUFSIZE 512 /* max length of response (RFC1939) */
28 #define HEADER_END(p) ((p)[0] == '\n' && ((p)[1] != ' ' && (p)[1] != '\t'))
30 #ifdef TESTMAIN
31 static int verbose;
32 char *program_name = "rfc822";
33 #endif /* TESTMAIN */
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 */
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 */
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);
64 #ifndef TESTMAIN
65 if (outlevel >= O_DEBUG)
66 fprintf(stdout, "About to rewrite %s", buf);
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 */
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.
81 parendepth = state = 0;
82 has_host_part = has_bare_name_part = FALSE;
83 for (from = buf; *from; from++)
85 #ifdef TESTMAIN
86 if (verbose)
88 printf("state %d: %s", state, buf);
89 printf("%*s^\n", from - buf + 10, " ");
91 #endif /* TESTMAIN */
92 if (state != 2) {
93 if (*from == '(')
94 ++parendepth;
95 else if (*from == ')')
96 --parendepth;
99 if (!parendepth && !has_host_part)
100 switch (state)
102 case 0: /* before header colon */
103 if (*from == ':')
104 state = 1;
105 break;
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;
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.
121 else if ((*from == ',' || HEADER_END(from))
122 && has_bare_name_part
123 && !has_host_part
124 && last_nws != ';')
126 int hostlen;
127 char *p;
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;
143 else if (from[1] == '('
144 && has_bare_name_part
145 && !has_host_part
146 && last_nws != ';' && last_nws != ')')
148 parens_from = from;
150 else if (!isspace(*from))
151 has_bare_name_part = TRUE;
152 break;
154 case 2: /* we're in a string */
155 if (*from == '"')
156 state = 1;
157 break;
159 case 3: /* we're in a <>-enclosed address */
160 if (*from == '@')
161 has_host_part = TRUE;
162 else if (*from == '>' && from[-1] != '<')
164 state = 1;
165 if (!has_host_part)
167 int hostlen;
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;
178 break;
182 * If we passed a comma, reset everything.
184 if (from[-1] == ',' && !parendepth) {
185 has_host_part = has_bare_name_part = FALSE;
186 parens_from = NULL;
190 #ifndef TESTMAIN
191 if (outlevel >= O_DEBUG)
192 fprintf(stdout, "Rewritten version is %s\n", buf);
193 #endif /* TESTMAIN */
194 return(buf);
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 */
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;
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 */
217 if (hdr)
219 hp = hdr;
220 state = START_HDR;
221 #ifdef TESTMAIN
222 orighdr = hdr;
223 #endif /* TESTMAIN */
224 tp = address;
227 for (; *hp; hp++)
229 #ifdef TESTMAIN
230 if (verbose)
232 printf("state %d: %s", state, orighdr);
233 printf("%*s^\n", hp - orighdr + 10, " ");
235 #endif /* TESTMAIN */
237 if (state == ENDIT_ALL) /* after last address */
238 return(NULL);
239 else if (HEADER_END(hp))
241 state = ENDIT_ALL;
242 if (tp > address)
244 while (isspace(*--tp))
245 continue;
246 *++tp = '\0';
248 return(tp > address ? (tp = address) : (char *)NULL);
250 else if (*hp == '\\') /* handle RFC822 escaping */
252 if (state != INSIDE_PARENS)
254 *tp++ = *hp++; /* take the escape */
255 *tp++ = *hp; /* take following char */
258 else switch (state)
260 case START_HDR: /* before header colon */
261 if (*hp == ':')
262 state = SKIP_JUNK;
263 break;
265 case SKIP_JUNK: /* looking for address start */
266 if (*hp == '"') /* quoted string */
268 oldstate = SKIP_JUNK;
269 state = INSIDE_DQUOTE;
270 *tp++ = *hp;
272 else if (*hp == '(') /* address comment -- ignore */
274 parendepth = 1;
275 oldstate = SKIP_JUNK;
276 state = INSIDE_PARENS;
278 else if (*hp == '<') /* begin <address> */
280 state = INSIDE_BRACKETS;
281 tp = address;
283 else if (*hp != ',' && !isspace(*hp))
285 --hp;
286 state = BARE_ADDRESS;
288 break;
290 case BARE_ADDRESS: /* collecting address without delimiters */
291 if (*hp == ',') /* end of address */
293 if (tp > address)
295 *tp++ = '\0';
296 state = SKIP_JUNK;
297 return(tp = address);
300 else if (*hp == '(') /* beginning of comment */
302 parendepth = 1;
303 oldstate = BARE_ADDRESS;
304 state = INSIDE_PARENS;
306 else if (*hp == '<') /* beginning of real address */
308 state = INSIDE_BRACKETS;
309 tp = address;
311 else if (!isspace(*hp)) /* just take it, ignoring whitespace */
312 *tp++ = *hp;
313 break;
315 case INSIDE_DQUOTE: /* we're in a quoted string, copy verbatim */
316 if (*hp != '"')
317 *tp++ = *hp;
318 else
320 *tp++ = *hp;
321 state = oldstate;
323 break;
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;
334 case INSIDE_BRACKETS: /* possible <>-enclosed address */
335 if (*hp == '>') /* end of address */
337 *tp++ = '\0';
338 state = SKIP_JUNK;
339 ++hp;
340 return(tp = address);
342 else if (*hp == '<') /* nested <> */
343 tp = address;
344 else if (*hp == '"') /* quoted address */
346 *tp++ = *hp;
347 oldstate = INSIDE_BRACKETS;
348 state = INSIDE_DQUOTE;
350 else /* just copy address */
351 *tp++ = *hp;
352 break;
356 return(NULL);
359 #ifdef TESTMAIN
360 static void parsebuf(char *longbuf, int reply)
362 char *cp;
364 if (reply)
366 reply_hack(longbuf, "HOSTNAME.NET");
367 printf("Rewritten buffer: %s", longbuf);
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);
379 main(int argc, char *argv[])
381 char buf[MSGBUFSIZE], longbuf[BUFSIZ];
382 int ch, reply;
384 verbose = reply = FALSE;
385 while ((ch = getopt(argc, argv, "rv")) != EOF)
386 switch(ch)
388 case 'r':
389 reply = TRUE;
390 break;
392 case 'v':
393 verbose = TRUE;
394 break;
397 while (fgets(buf, sizeof(buf)-1, stdin))
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])
409 if (verbose)
410 fputs(longbuf, stdout);
411 parsebuf(longbuf, reply);
412 longbuf[0] = '\0';
415 if (longbuf[0])
417 if (verbose)
418 fputs(longbuf, stdout);
419 parsebuf(longbuf, reply);
422 #endif /* TESTMAIN */
424 /* rfc822.c end */