* New version 2.21.999
[alpine.git] / imap / src / c-client / rfc822.c
blobb4c72a36aeff874b071d75db10fed117dd415662
1 /* ========================================================================
2 * Copyright 2013-2018 Eduardo Chappa
3 * Copyright 2008-2010 Mark Crispin
4 * ========================================================================
5 */
7 /*
8 * Program: RFC 2822 and MIME routines
10 * Author: Mark Crispin
12 * Date: 27 July 1988
13 * Last Edited: 30 September 2010
15 * Previous versions of this file were:
17 * Copyright 1988-2008 University of Washington
19 * Licensed under the Apache License, Version 2.0 (the "License");
20 * you may not use this file except in compliance with the License.
21 * You may obtain a copy of the License at
23 * http://www.apache.org/licenses/LICENSE-2.0
25 * This original version of this file is
26 * Copyright 1988 Stanford University
27 * and was developed in the Symbolic Systems Resources Group of the Knowledge
28 * Systems Laboratory at Stanford University in 1987-88, and was funded by the
29 * Biomedical Research Technology Program of the NationalInstitutes of Health
30 * under grant number RR-00785.
34 #include <ctype.h>
35 #include <stdio.h>
36 #include <time.h>
37 #include "c-client.h"
40 /* internal prototype */
41 void initialize_body(BODY *b, char *h, char *t);
44 /* Support for deprecated features in earlier specifications. Note that this
45 * module follows RFC 2822, and all use of "rfc822" in function names is
46 * for compatibility. Only the code identified by the conditionals below
47 * follows the earlier documents.
50 #define RFC733 1 /* parse "at" */
51 #define RFC822 0 /* generate A-D-L (MUST be 0 for 2822) */
53 /* RFC-822 static data */
55 #define RFC822CONT " " /* RFC 2822 continuation */
57 /* should have been "Remailed-" */
58 #define RESENTPREFIX "ReSent-"
59 static char *resentprefix = RESENTPREFIX;
60 /* syntax error host string */
61 static const char *errhst = ERRHOST;
64 /* Body formats constant strings, must match definitions in mail.h */
66 char *body_types[TYPEMAX+1] = {
67 "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
68 "MODEL", "X-UNKNOWN"
72 char *body_encodings[ENCMAX+1] = {
73 "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
77 /* Token delimiting special characters */
79 /* RFC 2822 specials */
80 const char *specials = " ()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
81 /* RFC 2822 phrase specials (no space) */
82 const char *rspecials = "()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
83 /* RFC 2822 dot-atom specials (no dot) */
84 const char *wspecials = " ()<>@,;:\\\"[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
85 /* RFC 2045 MIME body token specials */
86 const char *tspecials = " ()<>@,;:\\\"[]/?=\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
88 /* Subtype defaulting (a no-no, but regretably necessary...)
89 * Accepts: type code
90 * Returns: default subtype name
93 char *rfc822_default_subtype (unsigned short type)
95 switch (type) {
96 case TYPETEXT: /* default is TEXT/PLAIN */
97 return "PLAIN";
98 case TYPEMULTIPART: /* default is MULTIPART/MIXED */
99 return "MIXED";
100 case TYPEMESSAGE: /* default is MESSAGE/RFC822 */
101 return "RFC822";
102 case TYPEAPPLICATION: /* default is APPLICATION/OCTET-STREAM */
103 return "OCTET-STREAM";
104 case TYPEAUDIO: /* default is AUDIO/BASIC */
105 return "BASIC";
106 default: /* others have no default subtype */
107 return "UNKNOWN";
111 /* RFC 2822 parsing routines */
114 /* Parse an RFC 2822 message
115 * Accepts: pointer to return envelope
116 * pointer to return body
117 * pointer to header
118 * header byte count
119 * pointer to body stringstruct
120 * pointer to local host name
121 * recursion depth
122 * source driver flags
125 void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
126 STRING *bs,char *host,unsigned long depth,
127 unsigned long flags)
129 char c,*t,*d;
130 char *tmp = (char *) fs_get ((size_t) i + 100);
131 ENVELOPE *env = (*en = mail_newenvelope ());
132 BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
133 long MIMEp = -1; /* flag that MIME semantics are in effect */
134 long PathP = NIL; /* flag that a Path: was seen */
135 parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL);
136 if (!host) host = BADHOST; /* make sure that host is non-null */
137 while (i && *s != '\n') { /* until end of header */
138 t = tmp; /* initialize buffer pointer */
139 c = ' '; /* and previous character */
140 while (i && c) { /* collect text until logical end of line */
141 switch (c = *s++) { /* slurp a character */
142 case '\015': /* return, possible end of logical line */
143 if (*s == '\n') break; /* ignore if LF follows */
144 case '\012': /* LF, possible end of logical line */
145 /* tie off unless next line starts with WS */
146 if (*s != ' ' && *s != '\t') *t++ = c = '\0';
147 break;
148 case '\t': /* tab */
149 *t++ = ' '; /* coerce to space */
150 break;
151 default: /* all other characters */
152 *t++ = c; /* insert the character into the line */
153 break;
155 if (!--i) *t++ = '\0'; /* see if end of header */
158 /* find header item type */
159 if ((t = d = strchr (tmp,':')) != NULL) {
160 *d++ = '\0'; /* tie off header item, point at its data */
161 while (*d == ' ') d++; /* flush whitespace */
162 while ((tmp < t--) && (*t == ' ')) *t = '\0';
163 ucase (tmp); /* coerce to uppercase */
164 /* external callback */
165 if (pl) (*pl) (env,tmp,d,host);
166 switch (*tmp) { /* dispatch based on first character */
167 case '>': /* possible >From: */
168 if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
169 break;
170 case 'B': /* possible bcc: */
171 if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
172 break;
173 case 'C': /* possible cc: or Content-<mumble>*/
174 if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
175 else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
176 (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
177 (tmp[7] == '-') && body)
178 switch (MIMEp) {
179 case -1: /* unknown if MIME or not */
180 if (!(MIMEp = /* see if MIME-Version header exists */
181 search ((unsigned char *) s-1,i,
182 (unsigned char *)"\012MIME-Version",(long) 13))) {
183 #if 1
184 /* This is a disgusting kludge, and most of the messages which
185 * benefit from it are spam.
187 if (!strcmp (tmp+8,"TRANSFER-ENCODING") ||
188 (!strcmp (tmp+8,"TYPE") && strchr (d,'/'))) {
189 MM_LOG ("Warning: MIME header encountered in non-MIME message",
190 PARSE);
191 MIMEp = 1; /* declare MIME now */
193 else
194 #endif
195 break; /* non-MIME message */
197 case T: /* definitely MIME */
198 rfc822_parse_content_header (body,tmp+8,d);
200 break;
201 case 'D': /* possible Date: */
202 if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
203 break;
204 case 'F': /* possible From: */
205 if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
206 else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
207 t = env->followup_to = (char *) fs_get (1 + strlen (d));
208 while ((c = *d++) != '\0') if (c != ' ') *t++ = c;
209 *t++ = '\0';
211 break;
212 case 'I': /* possible In-Reply-To: */
213 if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
214 env->in_reply_to = cpystr (d);
215 break;
217 case 'M': /* possible Message-ID: or MIME-Version: */
218 if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
219 env->message_id = cpystr (d);
220 else if (!strcmp (tmp+1,"IME-VERSION")) {
221 /* tie off at end of phrase */
222 if ((t = rfc822_parse_phrase (d)) != NULL) *t = '\0';
223 rfc822_skipws (&d); /* skip whitespace */
224 /* known version? */
225 if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
226 MM_LOG ("Warning: message has unknown MIME version",PARSE);
227 MIMEp = T; /* note that we are MIME */
229 break;
230 case 'N': /* possible Newsgroups: */
231 if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
232 t = env->newsgroups = (char *) fs_get (1 + strlen (d));
233 while ((c = *d++) != '\0') if (c != ' ') *t++ = c;
234 *t++ = '\0';
236 break;
237 case 'P': /* possible Path: */
238 if (!strcmp (tmp+1,"ATH")) env->ngpathexists = T;
239 break;
240 case 'R': /* possible Reply-To: */
241 if (!strcmp (tmp+1,"EPLY-TO"))
242 rfc822_parse_adrlist (&env->reply_to,d,host);
243 else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
244 env->references = cpystr (d);
245 break;
246 case 'S': /* possible Subject: or Sender: */
247 if (!env->subject && !strcmp (tmp+1,"UBJECT"))
248 env->subject = cpystr (d);
249 else if (!strcmp (tmp+1,"ENDER"))
250 rfc822_parse_adrlist (&env->sender,d,host);
251 break;
252 case 'T': /* possible To: */
253 if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
254 break;
255 default:
256 break;
260 fs_give ((void **) &tmp); /* done with scratch buffer */
261 /* default Sender: and Reply-To: to From: */
262 if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
263 if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
264 /* now parse the body */
265 if (body) rfc822_parse_content (body,bs,host,depth,flags);
268 void
269 initialize_body(BODY *b, char *h, char *t)
271 if(b->type==TYPEMULTIPART){
272 PART *p;
273 cpytxt(&b->mime.text, h+b->mime.offset, b->mime.text.size);
274 cpytxt(&b->contents.text, t + b->contents.offset, b->size.bytes);
276 for(p=b->nested.part; p; p=p->next)
277 initialize_body((BODY *)p, h, t);
279 else {
280 cpytxt(&b->mime.text, h+b->mime.offset, b->mime.text.size);
281 cpytxt(&b->contents.text, t + b->contents.offset, b->size.bytes);
285 /* Parse a message body content
286 * Accepts: pointer to body structure
287 * body string
288 * pointer to local host name
289 * recursion depth
290 * source driver flags
293 void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
294 unsigned long flags)
296 char c,c1,*s,*s1;
297 int f;
298 unsigned long i,j,k,m;
299 PARAMETER *param;
300 PART *part = NIL;
301 if (depth > MAXMIMEDEPTH) { /* excessively deep recursion? */
302 body->type = TYPETEXT; /* yes, probably a malicious MIMEgram */
303 MM_LOG ("Ignoring excessively deep MIME recursion",PARSE);
305 if (!body->subtype) /* default subtype if still unknown */
306 body->subtype = cpystr (rfc822_default_subtype (body->type));
307 /* calculate offset */
308 if (bs->size >= (body->contents.offset = GETPOS (bs))) {
309 body->contents.text.size = i = SIZE (bs);
310 body->size.bytes = (flags & DR_CRLF) ? i : strcrlflen (bs);
312 else { /* paranoia code */
313 #if 0
314 fatal("rfc822_parse_content botch");
315 #endif
316 body->size.bytes = body->contents.text.size = i = 0;
317 body->contents.offset = bs->size;
318 SETPOS(bs,bs->size);
321 switch (body->type) { /* see if anything else special to do */
322 case TYPETEXT: /* text content */
323 if (!body->parameter) { /* no parameters set */
324 body->parameter = mail_newbody_parameter ();
325 body->parameter->attribute = cpystr ("CHARSET");
326 while (i && i--) { /* count lines and guess charset */
327 c = SNX (bs); /* get current character */
328 /* charset still unknown? */
329 if (!body->parameter->value) {
330 if ((c == I2C_ESC) && (i && i--) && ((c = SNX (bs)) == I2C_MULTI) &&
331 (i && i--) && (((c = SNX (bs)) == I2CS_94x94_JIS_NEW) ||
332 (c == I2CS_94x94_JIS_OLD)))
333 body->parameter->value = cpystr ("ISO-2022-JP");
334 else if (c & 0x80) body->parameter->value = cpystr ("X-UNKNOWN");
336 if (c == '\n') body->size.lines++;
338 /* 7-bit content */
339 if (!body->parameter->value) switch (body->encoding) {
340 case ENC7BIT: /* unadorned 7-bit */
341 case ENC8BIT: /* unadorned 8-bit (but 7-bit content) */
342 case ENCBINARY: /* binary (but 7-bit content( */
343 body->parameter->value = cpystr ("US-ASCII");
344 break;
345 default: /* QUOTED-PRINTABLE, BASE64, etc. */
346 body->parameter->value = cpystr ("X-UNKNOWN");
347 break;
350 /* just count lines */
351 else while (i && i--) if ((SNX (bs)) == '\n') body->size.lines++;
352 break;
354 case TYPEMESSAGE: /* encapsulated message */
355 /* encapsulated RFC-822 message? */
356 if (!strcmp (body->subtype,"RFC822")) {
357 body->nested.msg = mail_newmsg ();
358 switch (body->encoding) { /* make sure valid encoding */
359 case ENC7BIT: /* these are valid nested encodings */
360 case ENC8BIT:
361 case ENCBINARY:
362 case ENCBASE64: /* message/rfc822 message encoded as base64 */
363 break;
364 default:
365 MM_LOG ("Ignoring nested encoding of message contents",PARSE);
368 for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
369 j++) if ((c1 = SNX (bs)) != '\015') c = c1;
371 switch(body->encoding){
372 case ENCBASE64:
373 SETPOS (bs,body->contents.offset);
374 s = (char *) fs_get ((size_t) j + 1);
375 for (s1 = s,k = j; k--; *s1++ = SNX (bs));
376 s[j] = '\0'; /* decode encoded text */
377 if((s1 = strstr(s, "--")) != NULL)
378 *(s1-2) = '\0';
379 s1 = (char *) rfc822_base64 (s, strlen(s), &k);
380 if(s1){
381 char *t = strstr(s1, "\r\n\r\n");
383 if(t != NULL){
384 char *u;
385 STRING b;
387 t += 4;
388 u = cpystr(t);
389 INIT(&b, mail_string, t, strlen(t));
390 rfc822_parse_msg_full(&body->nested.msg->env, &body->nested.msg->body, t,
391 strlen(t), &b, BADHOST, 0, 0);
392 initialize_body(body->nested.msg->body, u, u);
394 fs_give((void **)&s1);
396 fs_give((void **)&s);
397 break;
399 default:
400 if (i > j) { /* unless no more text */
401 c1 = SNX (bs); /* body starts here */
402 j++; /* advance count */
404 /* note body text offset and header size */
405 body->nested.msg->header.text.size = j;
406 body->nested.msg->text.text.size = body->contents.text.size - j;
407 body->nested.msg->text.offset = GETPOS (bs);
408 body->nested.msg->full.offset = body->nested.msg->header.offset =
409 body->contents.offset;
410 body->nested.msg->full.text.size = body->contents.text.size;
411 /* copy header string */
412 SETPOS (bs,body->contents.offset);
413 s = (char *) fs_get ((size_t) j + 1);
414 for (s1 = s,k = j; k--; *s1++ = SNX (bs));
415 s[j] = '\0'; /* tie off string (not really necessary) */
416 /* now parse the body */
417 rfc822_parse_msg_full (&body->nested.msg->env,&body->nested.msg->body,s,
418 j,bs,h,depth+1,flags);
419 fs_give ((void **) &s); /* free header string */
421 /* restore position */
422 SETPOS (bs,body->contents.offset);
424 /* count number of lines */
425 while (i--) if (SNX (bs) == '\n') body->size.lines++;
426 break;
428 case TYPEMULTIPART: /* multiple parts */
429 switch (body->encoding) { /* make sure valid encoding */
430 case ENC7BIT: /* these are valid nested encodings */
431 case ENC8BIT:
432 case ENCBINARY:
433 break;
434 default:
435 MM_LOG ("Ignoring nested encoding of multipart contents",PARSE);
437 /* remember if digest */
438 f = !strcmp (body->subtype,"DIGEST");
439 /* find cookie */
440 for (s1 = NIL,param = body->parameter; param && !s1; param = param->next)
441 if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value;
442 if (!s1) s1 = "-"; /* yucky default */
443 j = strlen (s1) + 2; /* length of cookie and header */
444 c = '\012'; /* initially at beginning of line */
445 while (i > j) { /* examine data */
446 if ((m = GETPOS (bs)) != 0L) m--; /* get position in front of character */
447 switch (c) { /* examine each line */
448 case '\015': /* handle CRLF form */
449 if (CHR (bs) == '\012'){/* following LF? */
450 c = SNX (bs); i--; /* yes, slurp it */
452 case '\012': /* at start of a line, start with -- ? */
453 if (!(i && i-- && ((c = SNX (bs)) == '-') && i-- &&
454 ((c = SNX (bs)) == '-'))) break;
455 /* see if cookie matches */
456 if ((k = j - 2) != 0L) for (s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
457 if (k) break; /* strings didn't match if non-zero */
458 /* terminating delimiter? */
459 if ((c = ((i && i--) ? (SNX (bs)) : '\012')) == '-') {
460 if ((i && i--) && ((c = SNX (bs)) == '-') &&
461 ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) {
462 /* if have a final part calculate its size */
463 if (part) part->body.mime.text.size =
464 (m > part->body.mime.offset) ? (m - part->body.mime.offset) :0;
465 part = NIL; i = 1; /* terminate scan */
467 break;
470 /* swallow trailing whitespace */
471 while ((c == ' ') || (c == '\t'))
472 c = ((i && i--) ? (SNX (bs)) : '\012');
473 switch (c) { /* need newline after all of it */
474 case '\015': /* handle CRLF form */
475 if (i && CHR (bs) == '\012') {
476 c = SNX (bs); i--;/* yes, slurp it */
478 case '\012': /* new line */
479 if (part) { /* calculate size of previous */
480 part->body.mime.text.size =
481 (m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0;
482 /* instantiate next */
483 part = part->next = mail_newbody_part ();
484 } /* otherwise start new list */
485 else part = body->nested.part = mail_newbody_part ();
486 /* digest has a different default */
487 if (f) part->body.type = TYPEMESSAGE;
488 /* note offset from main body */
489 part->body.mime.offset = GETPOS (bs);
490 break;
491 default: /* whatever it was it wasn't valid */
492 break;
494 break;
495 default: /* not at a line */
496 c = SNX (bs); i--; /* get next character */
497 break;
501 /* calculate size of any final part */
502 if (part) part->body.mime.text.size = i +
503 ((GETPOS(bs) > part->body.mime.offset) ?
504 (GETPOS(bs) - part->body.mime.offset) : 0);
505 /* make a scratch buffer */
506 s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN));
507 /* in case empty multipart */
508 if (!body->nested.part) body->nested.part = mail_newbody_part ();
509 /* parse non-empty body parts */
510 for (part = body->nested.part; part; part = part->next) {
511 /* part non-empty (header and/or content)? */
512 if ((i = part->body.mime.text.size) != 0L) {
513 /* move to that part of the body */
514 SETPOS (bs,part->body.mime.offset);
515 /* until end of header */
516 while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) {
517 /* collect text until logical end of line */
518 for (j = 0,c = ' '; c; ) {
519 /* make sure buffer big enough */
520 if (j > (k - 10)) fs_resize ((void **) &s1,k += MAILTMPLEN);
521 switch (c1 = SNX (bs)) {
522 case '\015': /* return */
523 if (i && (CHR (bs) == '\012')) {
524 c1 = SNX (bs); /* eat any LF following */
525 i--;
527 case '\012': /* newline, possible end of logical line */
528 /* tie off unless continuation */
529 if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
530 s1[j] = c = '\0';
531 break;
532 case '\t': /* tab */
533 case ' ': /* insert whitespace if not already there */
534 if (c != ' ') s1[j++] = c = ' ';
535 break;
536 default: /* all other characters */
537 s1[j++] = c = c1; /* insert the character into the line */
538 break;
540 /* end of data ties off the header */
541 if (!i || !--i) s1[j++] = c = '\0';
543 /* find header item type */
544 if (((s1[0] == 'C') || (s1[0] == 'c')) &&
545 ((s1[1] == 'O') || (s1[1] == 'o')) &&
546 ((s1[2] == 'N') || (s1[2] == 'n')) &&
547 ((s1[3] == 'T') || (s1[3] == 't')) &&
548 ((s1[4] == 'E') || (s1[4] == 'e')) &&
549 ((s1[5] == 'N') || (s1[5] == 'n')) &&
550 ((s1[6] == 'T') || (s1[6] == 't')) &&
551 (s1[7] == '-') && (s = strchr (s1+8,':'))) {
552 /* tie off and flush whitespace */
553 for (*s++ = '\0'; *s == ' '; s++);
554 /* parse the header */
555 rfc822_parse_content_header (&part->body,ucase (s1+8),s);
559 /* skip header trailing (CR)LF */
560 if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
561 if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
562 j = bs->size; /* save upper level size */
563 /* set offset for next level, fake size to i */
564 bs->size = GETPOS (bs) + i;
565 part->body.mime.text.size -= i;
566 /* now parse it */
567 rfc822_parse_content (&part->body,bs,h,depth+1,flags);
568 bs->size = j; /* restore current level size */
570 else { /* zero-length part, use default subtype */
571 part->body.subtype = cpystr (rfc822_default_subtype (part->body.type));
572 /* see if anything else special to do */
573 switch (part->body.type) {
574 case TYPETEXT: /* text content */
575 /* default parameters */
576 if (!part->body.parameter) {
577 part->body.parameter = mail_newbody_parameter ();
578 part->body.parameter->attribute = cpystr ("CHARSET");
579 /* only assume US-ASCII if 7BIT */
580 part->body.parameter->value =
581 cpystr ((part->body.encoding == ENC7BIT) ?
582 "US-ASCII" : "X-UNKNOWN");
584 break;
585 case TYPEMESSAGE: /* encapsulated message in digest */
586 part->body.nested.msg = mail_newmsg ();
587 break;
588 default:
589 break;
593 fs_give ((void **) &s1); /* finished with scratch buffer */
594 break;
595 default: /* nothing special to do in any other case */
596 break;
600 /* Parse RFC 2822 body content header
601 * Accepts: body to write to
602 * possible content name
603 * remainder of header
606 void rfc822_parse_content_header (BODY *body,char *name,char *s)
608 char c,*t,tmp[MAILTMPLEN];
609 long i;
610 STRINGLIST *stl;
611 rfc822_skipws (&s); /* skip leading comments */
612 /* flush whitespace */
613 if ((t = strchr (name,' ')) != NULL) *t = '\0';
614 switch (*name) { /* see what kind of content */
615 case 'I': /* possible Content-ID */
616 if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
617 break;
618 case 'D': /* possible Content-Description */
619 if (!(strcmp (name+1,"ESCRIPTION") || body->description))
620 body->description = cpystr (s);
621 if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) {
622 /* get type word */
623 if (!(name = rfc822_parse_word (s,tspecials))) break;
624 c = *name; /* remember delimiter */
625 *name = '\0'; /* tie off type */
626 body->disposition.type = ucase (cpystr (s));
627 *name = c; /* restore delimiter */
628 rfc822_skipws (&name); /* skip whitespace */
629 rfc822_parse_parameter (&body->disposition.parameter,name);
631 break;
632 case 'L': /* possible Content-Language */
633 if (!(strcmp (name+1,"ANGUAGE") || body->language)) {
634 stl = NIL; /* process languages */
635 while (s && (name = rfc822_parse_word (s,tspecials))) {
636 c = *name; /* save delimiter */
637 *name = '\0'; /* tie off subtype */
638 if (stl) stl = stl->next = mail_newstringlist ();
639 else stl = body->language = mail_newstringlist ();
640 stl->text.data = (unsigned char *) ucase (cpystr (s));
641 stl->text.size = strlen ((char *) stl->text.data);
642 *name = c; /* restore delimiter */
643 rfc822_skipws (&name); /* skip whitespace */
644 if (*name == ',') { /* any more languages? */
645 s = ++name; /* advance to it them */
646 rfc822_skipws (&s);
648 else s = NIL; /* bogus or end of list */
651 else if (!(strcmp (name+1,"OCATION") || body->location))
652 body->location = cpystr (s);
653 break;
654 case 'M': /* possible Content-MD5 */
655 if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
656 break;
658 case 'T': /* possible Content-Type/Transfer-Encoding */
659 if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) {
660 /* get type word */
661 if (!(name = rfc822_parse_word (s,tspecials))) break;
662 c = *name; /* remember delimiter */
663 *name = '\0'; /* tie off type */
664 /* search for body type */
665 for (i = 0,s = rfc822_cpy (s);
666 (i <= TYPEMAX) && body_types[i] &&
667 compare_cstring (s,body_types[i]); i++);
668 if (i > TYPEMAX) { /* fell off end of loop? */
669 body->type = TYPEOTHER; /* coerce to X-UNKNOWN */
670 sprintf (tmp,"MIME type table overflow: %.100s",s);
671 MM_LOG (tmp,PARSE);
673 else { /* record body type index */
674 body->type = (unsigned short) i;
675 /* and name if new type */
676 if (body_types[body->type]) fs_give ((void **) &s);
677 else { /* major MIME body type unknown to us */
678 body_types[body->type] = ucase (s);
679 sprintf (tmp,"Unknown MIME type: %.100s",s);
680 MM_LOG (tmp,PARSE);
683 *name = c; /* restore delimiter */
684 rfc822_skipws (&name); /* skip whitespace */
685 if ((*name == '/') && /* subtype? */
686 (name = rfc822_parse_word ((s = ++name),tspecials))) {
687 c = *name; /* save delimiter */
688 *name = '\0'; /* tie off subtype */
689 rfc822_skipws (&s); /* copy subtype */
690 if (s) body->subtype = ucase (rfc822_cpy (s));
691 *name = c; /* restore delimiter */
692 rfc822_skipws (&name); /* skip whitespace */
694 else if (!name) { /* no subtype, was a subtype delimiter? */
695 name = s; /* barf, restore pointer */
696 rfc822_skipws (&name); /* skip leading whitespace */
698 rfc822_parse_parameter (&body->parameter,name);
701 else if (!strcmp (name+1,"RANSFER-ENCODING")) {
702 if (!(name = rfc822_parse_word (s,tspecials))) break;
703 c = *name; /* remember delimiter */
704 *name = '\0'; /* tie off encoding */
705 /* search for body encoding */
706 for (i = 0,s = rfc822_cpy (s);
707 (i <= ENCMAX) && body_encodings[i] &&
708 compare_cstring (s,body_encodings[i]); i++);
709 if (i > ENCMAX) { /* fell off end of loop? */
710 body->encoding = ENCOTHER;
711 sprintf (tmp,"MIME encoding table overflow: %.100s",s);
712 MM_LOG (tmp,PARSE);
714 else { /* record body encoding index */
715 body->encoding = (unsigned short) i;
716 /* and name if new encoding */
717 if (body_encodings[body->encoding]) fs_give ((void **) &s);
718 else {
719 body_encodings[body->encoding] = ucase (s);
720 sprintf (tmp,"Unknown MIME transfer encoding: %.100s",s);
721 MM_LOG (tmp,PARSE);
724 *name = c; /* restore delimiter */
725 /* ??check for cruft here?? */
727 break;
728 default: /* otherwise unknown */
729 break;
733 /* Parse RFC 2822 body parameter list
734 * Accepts: parameter list to write to
735 * text of list
738 void rfc822_parse_parameter (PARAMETER **par,char *text)
740 char c,*s,tmp[MAILTMPLEN];
741 PARAMETER *param = NIL;
742 /* parameter list? */
743 while (text && (*text == ';') &&
744 (text = rfc822_parse_word ((s = ++text),tspecials))) {
745 c = *text; /* remember delimiter */
746 *text = '\0'; /* tie off attribute name */
747 rfc822_skipws (&s); /* skip leading attribute whitespace */
748 if (!*s) *text = c; /* must have an attribute name */
749 else { /* instantiate a new parameter */
750 if (*par) param = param->next = mail_newbody_parameter ();
751 else param = *par = mail_newbody_parameter ();
752 param->attribute = ucase (cpystr (s));
753 *text = c; /* restore delimiter */
754 rfc822_skipws (&text); /* skip whitespace before equal sign */
755 if ((*text == '=') && /* make sure have value */
756 (text = rfc822_parse_word ((s = ++text),tspecials))) {
757 c = *text; /* remember delimiter */
758 *text = '\0'; /* tie off value */
759 rfc822_skipws (&s); /* skip leading value whitespace */
760 if (*s) param->value = rfc822_cpy (s);
761 *text = c; /* restore delimiter */
762 rfc822_skipws (&text);
764 if (!param->value) { /* value not found? */
765 param->value = cpystr ("MISSING_PARAMETER_VALUE");
766 sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
767 MM_LOG (tmp,PARSE);
771 /* string not present */
772 if (!text) MM_LOG ("Missing parameter",PARSE);
773 else if (*text) { /* must be end of poop */
774 sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
775 MM_LOG (tmp,PARSE);
779 /* Parse RFC 2822 address list
780 * Accepts: address list to write to
781 * input string
782 * default host name
785 void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
787 int c;
788 char *s,tmp[MAILTMPLEN];
789 ADDRESS *last = *lst;
790 ADDRESS *adr;
791 if (!string) return; /* no string */
792 rfc822_skipws (&string); /* skip leading WS */
793 if (!*string) return; /* empty string */
794 /* run to tail of list */
795 if (last) while (last->next) last = last->next;
796 while (string) { /* loop until string exhausted */
797 while (*string == ',') { /* RFC 822 allowed null addresses!! */
798 ++string; /* skip the comma */
799 rfc822_skipws (&string); /* and any leading WS */
801 if (!*string) string = NIL; /* punt if ran out of string */
802 /* got an address? */
803 else if ((adr = rfc822_parse_address (lst,last,&string,host,0)) != NULL) {
804 last = adr; /* new tail address */
805 if (string) { /* analyze what follows */
806 rfc822_skipws (&string);
807 switch (c = *(unsigned char *) string) {
808 case ',': /* comma? */
809 ++string; /* then another address follows */
810 break;
811 default:
812 s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
813 "Unexpected characters at end of address: %.80s";
814 sprintf (tmp,s,string);
815 MM_LOG (tmp,PARSE);
816 last = last->next = mail_newaddr ();
817 last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
818 last->host = cpystr (errhst);
819 /* falls through */
820 case '\0': /* null-specified address? */
821 string = NIL; /* punt remainder of parse */
822 break;
826 else if (string) { /* bad mailbox */
827 rfc822_skipws (&string); /* skip WS */
828 if (!*string) strcpy (tmp,"Missing address after comma");
829 else sprintf (tmp,"Invalid mailbox list: %.80s",string);
830 MM_LOG (tmp,PARSE);
831 string = NIL;
832 (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
833 adr->host = cpystr (errhst);
834 if (last) last = last->next = adr;
835 else *lst = last = adr;
836 break;
841 /* Parse RFC 2822 address
842 * Accepts: address list to write to
843 * tail of address list
844 * pointer to input string
845 * default host name
846 * group nesting depth
847 * Returns: new list tail
850 ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
851 char *defaulthost,unsigned long depth)
853 ADDRESS *adr;
854 if (!*string) return NIL; /* no string */
855 rfc822_skipws (string); /* skip leading WS */
856 if (!**string) return NIL; /* empty string */
857 if ((adr = rfc822_parse_group (lst,last,string,defaulthost,depth)) != NULL) last = adr;
858 /* got an address? */
859 else if ((adr = rfc822_parse_mailbox (string,defaulthost)) != NULL) {
860 if (!*lst) *lst = adr; /* yes, first time through? */
861 else last->next = adr; /* no, append to the list */
862 /* set for subsequent linking */
863 for (last = adr; last->next; last = last->next);
865 else if (*string) return NIL;
866 return last;
869 /* Parse RFC 2822 group
870 * Accepts: address list to write to
871 * pointer to tail of address list
872 * pointer to input string
873 * default host name
874 * group nesting depth
877 ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
878 char *defaulthost,unsigned long depth)
880 char tmp[MAILTMPLEN];
881 char *p,*s;
882 ADDRESS *adr;
883 if (depth > MAXGROUPDEPTH) { /* excessively deep recursion? */
884 MM_LOG ("Ignoring excessively deep group recursion",PARSE);
885 return NIL; /* probably abusive */
887 if (!*string) return NIL; /* no string */
888 rfc822_skipws (string); /* skip leading WS */
889 if (!**string || /* trailing whitespace or not group */
890 ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
891 return NIL;
892 s = p; /* end of candidate phrase */
893 rfc822_skipws (&s); /* find delimiter */
894 if (*s != ':') return NIL; /* not really a group */
895 *p = '\0'; /* tie off group name */
896 p = ++s; /* continue after the delimiter */
897 rfc822_skipws (&p); /* skip subsequent whitespace */
898 /* write as address */
899 (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
900 if (!*lst) *lst = adr; /* first time through? */
901 else last->next = adr; /* no, append to the list */
902 last = adr; /* set for subsequent linking */
903 *string = p; /* continue after this point */
904 while (*string && **string && (**string != ';')) {
905 if ((adr = rfc822_parse_address (lst,last,string,defaulthost,depth+1)) != NULL) {
906 last = adr; /* new tail address */
907 if (*string) { /* anything more? */
908 rfc822_skipws (string); /* skip whitespace */
909 switch (**string) { /* see what follows */
910 case ',': /* another address? */
911 ++*string; /* yes, skip past the comma */
912 case ';': /* end of group? */
913 case '\0': /* end of string */
914 break;
915 default:
916 sprintf (tmp,"Unexpected characters after address in group: %.80s",
917 *string);
918 MM_LOG (tmp,PARSE);
919 *string = NIL; /* cancel remainder of parse */
920 last = last->next = mail_newaddr ();
921 last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
922 last->host = cpystr (errhst);
926 else { /* bogon */
927 sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
928 MM_LOG (tmp,PARSE);
929 *string = NIL; /* cancel remainder of parse */
930 (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
931 adr->host = cpystr (errhst);
932 last = last->next = adr;
935 if (*string) { /* skip close delimiter */
936 if (**string == ';') ++*string;
937 rfc822_skipws (string);
939 /* append end of address mark to the list */
940 last->next = (adr = mail_newaddr ());
941 last = adr; /* set for subsequent linking */
942 return last; /* return the tail */
945 /* Parse RFC 2822 mailbox
946 * Accepts: pointer to string pointer
947 * default host
948 * Returns: address list
950 * Updates string pointer
953 ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
955 ADDRESS *adr = NIL;
956 char *s,*end;
957 parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL);
958 if (!*string) return NIL; /* no string */
959 rfc822_skipws (string); /* flush leading whitespace */
960 if (!**string) return NIL; /* empty string */
961 if (*(s = *string) == '<') /* note start, handle case of phraseless RA */
962 adr = rfc822_parse_routeaddr (s,string,defaulthost);
963 /* otherwise, expect at least one word */
964 else if ((end = rfc822_parse_phrase (s)) != NULL) {
965 if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) {
966 /* phrase is a personal name */
967 if (adr->personal) fs_give ((void **) &adr->personal);
968 *end = '\0'; /* tie off phrase */
969 adr->personal = rfc822_cpy (s);
971 /* call external phraseparser if phrase only */
972 else if (pp && rfc822_phraseonly (end) &&
973 (adr = (*pp) (s,end,defaulthost))) {
974 *string = end; /* update parse pointer */
975 rfc822_skipws (string); /* skip WS in the normal way */
977 else adr = rfc822_parse_addrspec (s,string,defaulthost);
979 return adr; /* return the address */
983 /* Check if address is a phrase only
984 * Accepts: pointer to end of phrase
985 * Returns: T if phrase only, else NIL;
988 long rfc822_phraseonly (char *end)
990 while (*end == ' ') ++end; /* call rfc822_skipws() instead?? */
991 switch (*end) {
992 case '\0': case ',': case ';':
993 return LONGT; /* is a phrase only */
995 return NIL; /* something other than phase is here */
998 /* Parse RFC 2822 route-address
999 * Accepts: string pointer
1000 * pointer to string pointer to update
1001 * Returns: address
1003 * Updates string pointer
1006 ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
1008 char tmp[MAILTMPLEN];
1009 ADDRESS *adr;
1010 char *s,*t,*adl;
1011 size_t adllen,i;
1012 if (!string) return NIL;
1013 rfc822_skipws (&string); /* flush leading whitespace */
1014 /* must start with open broket */
1015 if (*string != '<') return NIL;
1016 t = ++string; /* see if A-D-L there */
1017 rfc822_skipws (&t); /* flush leading whitespace */
1018 for (adl = NIL,adllen = 0; /* parse possible A-D-L */
1019 (*t == '@') && (s = rfc822_parse_domain (t+1,&t));) {
1020 i = strlen (s) + 2; /* @ plus domain plus delimiter or NUL */
1021 if (adl) { /* have existing A-D-L? */
1022 fs_resize ((void **) &adl,adllen + i);
1023 sprintf (adl + adllen - 1,",@%s",s);
1025 /* write initial A-D-L */
1026 else sprintf (adl = (char *) fs_get (i),"@%s",s);
1027 adllen += i; /* new A-D-L length */
1028 fs_give ((void **) &s); /* don't need domain any more */
1029 rfc822_skipws (&t); /* skip WS */
1030 if (*t != ',') break; /* put if not comma */
1031 t++; /* skip the comma */
1032 rfc822_skipws (&t); /* skip WS */
1034 if (adl) { /* got an A-D-L? */
1035 if (*t != ':') { /* make sure syntax good */
1036 sprintf (tmp,"Unterminated at-domain-list: %.80s%.80s",adl,t);
1037 MM_LOG (tmp,PARSE);
1039 else string = ++t; /* continue parse from this point */
1042 /* parse address spec */
1043 if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) {
1044 if (adl) fs_give ((void **) &adl);
1045 return NIL;
1047 if (adl) adr->adl = adl; /* have an A-D-L? */
1048 if (*ret) if (**ret == '>') { /* make sure terminated OK */
1049 ++*ret; /* skip past the broket */
1050 rfc822_skipws (ret); /* flush trailing WS */
1051 if (!**ret) *ret = NIL; /* wipe pointer if at end of string */
1052 return adr; /* return the address */
1054 sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
1055 *adr->host == '@' ? "<null>" : adr->host);
1056 MM_LOG (tmp,PARSE);
1057 adr->next = mail_newaddr ();
1058 adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
1059 adr->next->host = cpystr (errhst);
1060 return adr; /* return the address */
1063 /* Parse RFC 2822 address-spec
1064 * Accepts: string pointer
1065 * pointer to string pointer to update
1066 * default host
1067 * Returns: address
1069 * Updates string pointer
1072 ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
1074 ADDRESS *adr;
1075 char c,*s,*t,*v,*end;
1076 if (!string) return NIL; /* no string */
1077 rfc822_skipws (&string); /* flush leading whitespace */
1078 if (!*string) return NIL; /* empty string */
1079 /* find end of mailbox */
1080 if (!(t = rfc822_parse_word (string,wspecials))) return NIL;
1081 adr = mail_newaddr (); /* create address block */
1082 c = *t; /* remember delimiter */
1083 *t = '\0'; /* tie off mailbox */
1084 /* copy mailbox */
1085 adr->mailbox = rfc822_cpy (string);
1086 *t = c; /* restore delimiter */
1087 end = t; /* remember end of mailbox */
1088 rfc822_skipws (&t); /* skip whitespace */
1089 while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
1090 string = ++t; /* skip past the dot and any WS */
1091 rfc822_skipws (&string);
1092 /* get next word of mailbox */
1093 if ((t = rfc822_parse_word (string,wspecials)) != NULL) {
1094 end = t; /* remember new end of mailbox */
1095 c = *t; /* remember delimiter */
1096 *t = '\0'; /* tie off word */
1097 s = rfc822_cpy (string); /* copy successor part */
1098 *t = c; /* restore delimiter */
1099 /* build new mailbox */
1100 sprintf (v = (char *) fs_get (strlen (adr->mailbox) + strlen (s) + 2),
1101 "%s.%s",adr->mailbox,s);
1102 fs_give ((void **) &adr->mailbox);
1103 adr->mailbox = v; /* new host name */
1104 rfc822_skipws (&t); /* skip WS after mailbox */
1106 else { /* barf */
1107 MM_LOG ("Invalid mailbox part after .",PARSE);
1108 break;
1111 t = end; /* remember delimiter in case no host */
1113 rfc822_skipws (&end); /* sniff ahead at what follows */
1114 #if RFC733 /* RFC 733 used "at" instead of "@" */
1115 if (((*end == 'a') || (*end == 'A')) &&
1116 ((end[1] == 't') || (end[1] == 'T')) &&
1117 ((end[2] == ' ') || (end[2] == '\t') || (end[2] == '\015') ||
1118 (end[2] == '\012') || (end[2] == '(')))
1119 *++end = '@';
1120 #endif
1121 if (*end != '@') end = t; /* host name missing */
1122 /* otherwise parse host name */
1123 else if (!(adr->host = rfc822_parse_domain (++end,&end)))
1124 adr->host = cpystr (errhst);
1125 /* default host if missing */
1126 if (!adr->host) adr->host = cpystr (defaulthost ? defaulthost : "@");
1127 /* try person name in comments if missing */
1128 if (end && !(adr->personal && *adr->personal)) {
1129 while (*end == ' ') ++end; /* see if we can find a person name here */
1130 if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
1131 adr->personal = rfc822_cpy (s);
1132 rfc822_skipws (&end); /* skip any other WS in the normal way */
1134 /* set return to end pointer */
1135 *ret = (end && *end) ? end : NIL;
1136 return adr; /* return the address we got */
1139 /* Parse RFC 2822 domain
1140 * Accepts: string pointer
1141 * pointer to return end of domain
1142 * Returns: domain name or NIL if failure
1145 char *rfc822_parse_domain (char *string,char **end)
1147 char *ret = NIL;
1148 char c,*s,*t,*v;
1149 rfc822_skipws (&string); /* skip whitespace */
1150 if (*string == '[') { /* domain literal? */
1151 if (!(*end = rfc822_parse_word (string + 1,"]\\")))
1152 MM_LOG ("Empty domain literal",PARSE);
1153 else if (**end != ']') MM_LOG ("Unterminated domain literal",PARSE);
1154 else {
1155 size_t len = ++*end - string;
1156 strncpy (ret = (char *) fs_get (len + 1),string,len);
1157 ret[len] = '\0'; /* tie off literal */
1160 /* search for end of host */
1161 else if ((t = rfc822_parse_word (string,wspecials)) != NULL) {
1162 c = *t; /* remember delimiter */
1163 *t = '\0'; /* tie off host */
1164 ret = rfc822_cpy (string); /* copy host */
1165 *t = c; /* restore delimiter */
1166 *end = t; /* remember end of domain */
1167 rfc822_skipws (&t); /* skip WS after host */
1168 while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
1169 string = ++t; /* skip past the dot and any WS */
1170 rfc822_skipws (&string);
1171 if ((string = rfc822_parse_domain (string,&t)) != NULL) {
1172 *end = t; /* remember new end of domain */
1173 c = *t; /* remember delimiter */
1174 *t = '\0'; /* tie off host */
1175 s = rfc822_cpy (string);/* copy successor part */
1176 *t = c; /* restore delimiter */
1177 /* build new domain */
1178 sprintf (v = (char *) fs_get (strlen (ret) + strlen (s) + 2),
1179 "%s.%s",ret,s);
1180 fs_give ((void **) &ret);
1181 ret = v; /* new host name */
1182 rfc822_skipws (&t); /* skip WS after domain */
1184 else { /* barf */
1185 MM_LOG ("Invalid domain part after .",PARSE);
1186 break;
1190 else MM_LOG ("Missing or invalid host name after @",PARSE);
1191 return ret;
1194 /* Parse RFC 2822 phrase
1195 * Accepts: string pointer
1196 * Returns: pointer to end of phrase
1199 char *rfc822_parse_phrase (char *s)
1201 char *curpos;
1202 if (!s) return NIL; /* no-op if no string */
1203 /* find first word of phrase */
1204 curpos = rfc822_parse_word (s,NIL);
1205 if (!curpos) return NIL; /* no words means no phrase */
1206 if (!*curpos) return curpos; /* check if string ends with word */
1207 s = curpos; /* sniff past the end of this word and WS */
1208 rfc822_skipws (&s); /* skip whitespace */
1209 /* recurse to see if any more */
1210 return (s = rfc822_parse_phrase (s)) ? s : curpos;
1213 /* Parse RFC 2822 word
1214 * Accepts: string pointer
1215 * delimiter (or NIL for phrase word parsing)
1216 * Returns: pointer to end of word
1219 char *rfc822_parse_word (char *s,const char *delimiters)
1221 char *st,*str;
1222 if (!s) return NIL; /* no string */
1223 rfc822_skipws (&s); /* flush leading whitespace */
1224 if (!*s) return NIL; /* empty string */
1225 str = s; /* hunt pointer for strpbrk */
1226 while (T) { /* look for delimiter, return if none */
1227 if (!(st = strpbrk (str,delimiters ? delimiters : wspecials)))
1228 return str + strlen (str);
1229 /* ESC in phrase */
1230 if (!delimiters && (*st == I2C_ESC)) {
1231 str = ++st; /* always skip past ESC */
1232 switch (*st) { /* special hack for RFC 1468 (ISO-2022-JP) */
1233 case I2C_MULTI: /* multi byte sequence */
1234 switch (*++st) {
1235 case I2CS_94x94_JIS_OLD:/* old JIS (1978) */
1236 case I2CS_94x94_JIS_NEW:/* new JIS (1983) */
1237 str = ++st; /* skip past the shift to JIS */
1238 while ((st = strchr (st,I2C_ESC)) != NULL)
1239 if ((*++st == I2C_G0_94) && ((st[1] == I2CS_94_ASCII) ||
1240 (st[1] == I2CS_94_JIS_ROMAN) ||
1241 (st[1] == I2CS_94_JIS_BUGROM))) {
1242 str = st += 2; /* skip past the shift back to ASCII */
1243 break;
1245 /* eats entire text if no shift back */
1246 if (!st || !*st) return str + strlen (str);
1248 break;
1249 case I2C_G0_94: /* single byte sequence */
1250 switch (st[1]) {
1251 case I2CS_94_ASCII: /* shift to ASCII */
1252 case I2CS_94_JIS_ROMAN: /* shift to JIS-Roman */
1253 case I2CS_94_JIS_BUGROM:/* old buggy definition of JIS-Roman */
1254 str = st + 2; /* skip past the shift */
1255 break;
1260 else switch (*st) { /* dispatch based on delimiter */
1261 case '"': /* quoted string */
1262 /* look for close quote */
1263 while (*++st != '"') switch (*st) {
1264 case '\0': /* unbalanced quoted string */
1265 return NIL; /* sick sick sick */
1266 case '\\': /* quoted character */
1267 if (!*++st) return NIL; /* skip the next character */
1268 default: /* ordinary character */
1269 break; /* no special action */
1271 str = ++st; /* continue parse */
1272 break;
1273 case '\\': /* quoted character */
1274 /* This is wrong; a quoted-pair can not be part of a word. However,
1275 * domain-literal is parsed as a word and quoted-pairs can be used
1276 * *there*. Either way, it's pretty pathological.
1278 if (st[1]) { /* not on NUL though... */
1279 str = st + 2; /* skip quoted character and go on */
1280 break;
1282 default: /* found a word delimiter */
1283 return (st == s) ? NIL : st;
1288 /* Copy an RFC 2822 format string
1289 * Accepts: string
1290 * Returns: copy of string
1293 char *rfc822_cpy (char *src)
1295 /* copy and unquote */
1296 return rfc822_quote (cpystr (src));
1300 /* Unquote an RFC 2822 format string
1301 * Accepts: string
1302 * Returns: string
1305 char *rfc822_quote (char *src)
1307 char *ret = src;
1308 if (strpbrk (src,"\\\"")) { /* any quoting in string? */
1309 char *dst = ret;
1310 while (*src) { /* copy string */
1311 if (*src == '\"') src++; /* skip double quote entirely */
1312 else {
1313 if (*src == '\\') src++;/* skip over single quote, copy next always */
1314 *dst++ = *src++; /* copy character */
1317 *dst = '\0'; /* tie off string */
1319 return ret; /* return our string */
1323 /* Copy address list
1324 * Accepts: address list
1325 * Returns: address list
1328 ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
1330 ADDRESS *dadr;
1331 ADDRESS *ret = NIL;
1332 ADDRESS *prev = NIL;
1333 while (adr) { /* loop while there's still an MAP adr */
1334 dadr = mail_newaddr (); /* instantiate a new address */
1335 if (!ret) ret = dadr; /* note return */
1336 if (prev) prev->next = dadr;/* tie on to the end of any previous */
1337 dadr->personal = cpystr (adr->personal);
1338 dadr->adl = cpystr (adr->adl);
1339 dadr->mailbox = cpystr (adr->mailbox);
1340 dadr->host = cpystr (adr->host);
1341 prev = dadr; /* this is now the previous */
1342 adr = adr->next; /* go to next address in list */
1344 return (ret); /* return the MTP address list */
1347 /* Skips RFC 2822 whitespace
1348 * Accepts: pointer to string pointer
1351 void rfc822_skipws (char **s)
1353 while (T) switch (**s) {
1354 case ' ': case '\t': case '\015': case '\012':
1355 ++*s; /* skip all forms of LWSP */
1356 break;
1357 case '(': /* start of comment */
1358 if (rfc822_skip_comment (s,(long) NIL)) break;
1359 default:
1360 return; /* end of whitespace */
1365 /* Skips RFC 2822 comment
1366 * Accepts: pointer to string pointer
1367 * trim flag
1368 * Returns: pointer to first non-blank character of comment
1371 char *rfc822_skip_comment (char **s,long trim)
1373 char *ret,tmp[MAILTMPLEN];
1374 char *s1 = *s;
1375 char *t = NIL;
1376 /* skip past whitespace */
1377 for (ret = ++s1; *ret == ' '; ret++);
1378 do switch (*s1) { /* get character of comment */
1379 case '(': /* nested comment? */
1380 if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
1381 t = --s1; /* last significant char at end of comment */
1382 break;
1383 case ')': /* end of comment? */
1384 *s = ++s1; /* skip past end of comment */
1385 if (trim) { /* if level 0, must trim */
1386 if (t) t[1] = '\0'; /* tie off comment string */
1387 else *ret = '\0'; /* empty comment */
1389 return ret;
1390 case '\\': /* quote next character? */
1391 if (*++s1) { /* next character non-null? */
1392 t = s1; /* update last significant character pointer */
1393 break; /* all OK */
1395 case '\0': /* end of string */
1396 sprintf (tmp,"Unterminated comment: %.80s",*s);
1397 MM_LOG (tmp,PARSE);
1398 **s = '\0'; /* nuke duplicate messages in case reparse */
1399 return NIL; /* this is wierd if it happens */
1400 case ' ': /* whitespace isn't significant */
1401 break;
1402 default: /* random character */
1403 t = s1; /* update last significant character pointer */
1404 break;
1405 } while (s1++);
1406 return NIL; /* impossible, but pacify lint et al */
1409 /* Buffered output routines */
1412 /* Output character to buffer
1413 * Accepts: buffer
1414 * character to write
1415 * Returns: T if success, NIL if error
1418 static long rfc822_output_char (RFC822BUFFER *buf,int c)
1420 if ((buf->cur == buf->end) && !rfc822_output_flush (buf)) return NIL;
1421 *buf->cur++ = c; /* add character, soutr buffer if full */
1422 return (buf->cur == buf->end) ? rfc822_output_flush (buf) : LONGT;
1426 /* Output data to buffer
1427 * Accepts: buffer
1428 * data to write
1429 * size of data
1430 * Returns: T if success, NIL if error
1433 static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len)
1435 while (len) { /* until request satified */
1436 long i;
1437 if ((i = min (len,buf->end - buf->cur)) != 0L) {
1438 memcpy (buf->cur,string,i);
1439 buf->cur += i; /* blat data */
1440 string += i;
1441 len -= i;
1443 /* soutr buffer now if full */
1444 if ((len || (buf->cur == buf->end)) && !rfc822_output_flush (buf))
1445 return NIL;
1447 return LONGT;
1450 /* Output string to buffer
1451 * Accepts: buffer
1452 * string to write
1453 * Returns: T if success, NIL if error
1456 static long rfc822_output_string (RFC822BUFFER *buf,char *string)
1458 return rfc822_output_data (buf,string,strlen (string));
1462 /* Flush buffer
1463 * Accepts: buffer
1464 * I/O routine
1465 * stream for I/O routine
1466 * Returns: T if success, NIL if error
1469 long rfc822_output_flush (RFC822BUFFER *buf)
1471 *buf->cur = '\0'; /* tie off buffer at this point */
1472 return (*buf->f) (buf->s,buf->cur = buf->beg);
1475 /* Message writing routines */
1478 /* Output RFC 822 message
1479 * Accepts: temporary buffer as a SIZEDTEXT
1480 * envelope
1481 * body
1482 * I/O routine
1483 * stream for I/O routine
1484 * non-zero if 8-bit output desired
1485 * Returns: T if successful, NIL if failure
1487 * This routine always uses standard specials for phrases and does not write
1488 * bcc entries, since it is called from the SMTP and NNTP routines. If you
1489 * need to do something different you need to arm an rfc822outfull_t and/or
1490 * rfc822out_t function.
1493 long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8)
1495 rfc822outfull_t r822of =
1496 (rfc822outfull_t) mail_parameters (NIL,GET_RFC822OUTPUTFULL,NIL);
1497 rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
1498 /* call external RFC 2822 output generator */
1499 if (r822of) return (*r822of) (buf,env,body,ok8);
1500 else if (r822o) return (*r822o) (buf->cur,env,body,buf->f,buf->s,ok8);
1501 /* encode body as necessary */
1502 if (ok8) rfc822_encode_body_8bit (env,body);
1503 else rfc822_encode_body_7bit (env,body);
1504 /* output header and body */
1505 return rfc822_output_header (buf,env,body,NIL,NIL) &&
1506 rfc822_output_text (buf,body) && rfc822_output_flush (buf);
1509 /* Output RFC 822 header
1510 * Accepts: buffer
1511 * envelope
1512 * body
1513 * non-standard specials to be used for phrases if non-NIL
1514 * flags (non-zero to include bcc
1515 * Returns: T if success, NIL if failure
1518 long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,
1519 const char *specials,long flags)
1521 long i = env->remail ? strlen (env->remail) : 0;
1522 return /* write header */
1523 (!i || /* snip extra CRLF from remail header */
1524 rfc822_output_data (buf,env->remail,
1525 ((i > 4) && (env->remail[i-4] == '\015')) ?
1526 i - 2 : i)) &&
1527 rfc822_output_header_line (buf,"Newsgroups",i,env->newsgroups) &&
1528 rfc822_output_header_line (buf,"Date",i,env->date) &&
1529 rfc822_output_address_line (buf,"From",i,env->from,specials) &&
1530 rfc822_output_address_line (buf,"Sender",i,env->sender,specials) &&
1531 rfc822_output_address_line (buf,"Reply-To",i,env->reply_to,specials) &&
1532 rfc822_output_header_line (buf,"Subject",i,env->subject) &&
1533 ((env->bcc && !(env->to || env->cc)) ?
1534 rfc822_output_string (buf,"To: undisclosed recipients: ;\015\012") :
1535 LONGT) &&
1536 rfc822_output_address_line (buf,"To",i,env->to,specials) &&
1537 rfc822_output_address_line (buf,"cc",i,env->cc,specials) &&
1538 (flags ? rfc822_output_address_line (buf,"bcc",i,env->bcc,specials) : T) &&
1539 rfc822_output_header_line (buf,"In-Reply-To",i,env->in_reply_to) &&
1540 rfc822_output_header_line (buf,"Message-ID",i,env->message_id) &&
1541 rfc822_output_header_line (buf,"Followup-to",i,env->followup_to) &&
1542 rfc822_output_header_line (buf,"References",i,env->references) &&
1543 (env->remail || !body ||
1544 (rfc822_output_string (buf,"MIME-Version: 1.0\015\012") &&
1545 rfc822_output_body_header (buf,body))) &&
1546 /* write terminating blank line */
1547 rfc822_output_string (buf,"\015\012");
1550 /* Output RFC 2822 header text line
1551 * Accepts: buffer
1552 * pointer to header type
1553 * non-NIL if resending
1554 * pointer to text
1555 * Returns: T if success, NIL if failure
1558 long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent,
1559 char *text)
1561 return !text ||
1562 ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1563 rfc822_output_string (buf,type) && rfc822_output_string (buf,": ") &&
1564 rfc822_output_string (buf,text) && rfc822_output_string (buf,"\015\012"));
1568 /* Output RFC 2822 header address line
1569 * Accepts: buffer
1570 * pointer to header type
1571 * non-NIL if resending
1572 * address(s) to interpret
1573 * non-standard specials to be used for phrases if non-NIL
1574 * Returns: T if success, NIL if failure
1577 long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent,
1578 ADDRESS *adr,const char *specials)
1580 long pretty = strlen (type);
1581 return !adr ||
1582 ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1583 rfc822_output_data (buf,type,pretty) && rfc822_output_string (buf,": ") &&
1584 rfc822_output_address_list (buf,adr,
1585 resent ? pretty + sizeof (RESENTPREFIX) - 1 :
1586 pretty,specials) &&
1587 rfc822_output_string (buf,"\015\012"));
1590 /* Output RFC 2822 address list
1591 * Accepts: buffer
1592 * pointer to address list
1593 * non-zero if pretty-printing
1594 * non-standard specials to be used for phrases if non-NIL
1595 * Returns: T if success, NIL if failure
1598 long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty,
1599 const char *specials)
1601 long n;
1602 /* default to rspecials */
1603 if (!specials) specials = rspecials;
1604 for (n = 0; adr; adr = adr->next) {
1605 char *base = buf->cur;
1606 if (adr->host) { /* ordinary address? */
1607 if (!(pretty && n)) { /* suppress if pretty and in group */
1608 if ( /* use phrase <route-addr> if phrase */
1609 #if RFC822
1610 adr->adl || /* or A-D-L */
1611 #endif
1612 (adr->personal && *adr->personal)) {
1613 if (!((adr->personal ? rfc822_output_cat (buf,adr->personal,
1614 rspecials) : LONGT) &&
1615 rfc822_output_string (buf," <") &&
1616 rfc822_output_address (buf,adr) &&
1617 rfc822_output_string (buf,">"))) return NIL;
1619 else if (!rfc822_output_address (buf,adr)) return NIL;
1620 if (adr->next && adr->next->mailbox &&
1621 !rfc822_output_string (buf,", ")) return NIL;
1624 else if (adr->mailbox) { /* start of group? */
1625 /* yes, write group */
1626 if (!(rfc822_output_cat (buf,adr->mailbox,rspecials) &&
1627 rfc822_output_string (buf,": "))) return NIL;
1628 ++n; /* in a group now */
1630 else if (n) { /* must be end of group (but be paranoid) */
1631 if (!rfc822_output_char (buf,';') ||
1632 ((!--n && adr->next && adr->next->mailbox) &&
1633 !rfc822_output_string (buf,", "))) return NIL;
1635 if (pretty && adr->next && /* pretty printing? */
1636 ((pretty += ((buf->cur > base) ? buf->cur - base :
1637 (buf->end - base) + (buf->cur - buf->beg))) >= 78)) {
1638 if (!(rfc822_output_string (buf,"\015\012") &&
1639 rfc822_output_string (buf,RFC822CONT))) return NIL;
1640 base = buf->cur; /* update base for pretty printing */
1641 pretty = sizeof (RFC822CONT) - 1;
1644 return LONGT;
1647 /* Write RFC 2822 route-address to string
1648 * Accepts: buffer
1649 * pointer to single address
1650 * Returns: T if success, NIL if failure
1653 long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr)
1655 return !adr || !adr->host ||
1657 #if RFC822 /* old code with A-D-L support */
1658 (!adr->adl || (rfc822_output_string (buf,adr->adl) &&
1659 rfc822_output_char (buf,':'))) &&
1660 #endif
1661 rfc822_output_cat (buf,adr->mailbox,NIL) &&
1662 ((*adr->host == '@') || /* unless null host (HIGHLY discouraged!) */
1663 (rfc822_output_char (buf,'@') &&
1664 rfc822_output_cat (buf,adr->host,NIL))));
1668 /* Output RFC 2822 string with concatenation
1669 * Accepts: buffer
1670 * string to concatenate
1671 * list of special characters or NIL for dot-atom format
1672 * Returns: T if success, NIL if failure
1675 long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials)
1677 char *s;
1678 if (!*src || /* empty string or any specials present? */
1679 (specials ? (T && strpbrk (src,specials)) :
1680 (strpbrk (src,wspecials) || (*src == '.') || strstr (src,"..") ||
1681 (src[strlen (src) - 1] == '.')))) {
1682 /* yes, write as quoted string*/
1683 if (!rfc822_output_char (buf,'"')) return NIL;
1684 /* embedded quote characters? */
1685 for (; (s = strpbrk (src,"\\\"")) != NULL; src = s + 1) {
1686 /* yes, insert quoting */
1687 if (!(rfc822_output_data (buf,src,s-src) &&
1688 rfc822_output_char (buf,'\\') &&
1689 rfc822_output_char (buf,*s))) return NIL;
1691 /* return string and trailing quote*/
1692 return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"');
1694 /* easy case */
1695 return rfc822_output_string (buf,src);
1698 /* Output MIME parameter list
1699 * Accepts: buffer
1700 * parameter list
1701 * Returns: T if success, NIL if failure
1704 long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param)
1706 while (param) {
1707 if (rfc822_output_string (buf,"; ") &&
1708 rfc822_output_string (buf,param->attribute) &&
1709 rfc822_output_char (buf,'=') &&
1710 rfc822_output_cat (buf,param->value,tspecials)) param = param->next;
1711 else return NIL;
1713 return LONGT;
1717 /* Output RFC 2822 stringlist
1718 * Accepts: buffer
1719 * stringlist
1720 * Returns: T if success, NIL if failure
1723 long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl)
1725 while (stl)
1726 if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) ||
1727 ((stl = stl->next) && !rfc822_output_string (buf,", ")))
1728 return NIL;
1729 return LONGT;
1732 /* Output body content header
1733 * Accepts: buffer
1734 * body to interpret
1735 * Returns: T if success, NIL if failure
1738 long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body)
1740 return /* type and subtype*/
1741 rfc822_output_string (buf,"Content-Type: ") &&
1742 rfc822_output_string (buf,body_types[body->type]) &&
1743 rfc822_output_char (buf,'/') &&
1744 rfc822_output_string (buf,body->subtype ? body->subtype :
1745 rfc822_default_subtype (body->type)) &&
1746 /* parameters (w/ US-ASCII default */
1747 (body->parameter ? rfc822_output_parameter (buf,body->parameter) :
1748 ((body->type != TYPETEXT) ||
1749 (rfc822_output_string (buf,"; CHARSET=") &&
1750 rfc822_output_string (buf,(body->encoding == ENC7BIT) ?
1751 "US-ASCII" : "X-UNKNOWN")))) &&
1752 (!body->encoding || /* note: 7BIT never output as encoding! */
1753 (rfc822_output_string (buf,"\015\012Content-Transfer-Encoding: ") &&
1754 rfc822_output_string (buf,body_encodings[body->encoding]))) &&
1755 (!body->id || /* identification */
1756 (rfc822_output_string (buf,"\015\012Content-ID: ") &&
1757 rfc822_output_string (buf,body->id))) &&
1758 (!body->description || /* description */
1759 (rfc822_output_string (buf,"\015\012Content-Description: ") &&
1760 rfc822_output_string (buf,body->description))) &&
1761 (!body->md5 || /* MD5 checksum */
1762 (rfc822_output_string (buf,"\015\012Content-MD5: ") &&
1763 rfc822_output_string (buf,body->md5))) &&
1764 (!body->language || /* language */
1765 (rfc822_output_string (buf,"\015\012Content-Language: ") &&
1766 rfc822_output_stringlist (buf,body->language))) &&
1767 (!body->location || /* location */
1768 (rfc822_output_string (buf,"\015\012Content-Location: ") &&
1769 rfc822_output_string (buf,body->location))) &&
1770 (!body->disposition.type || /* disposition */
1771 (rfc822_output_string (buf,"\015\012Content-Disposition: ") &&
1772 rfc822_output_string (buf,body->disposition.type) &&
1773 rfc822_output_parameter (buf,body->disposition.parameter))) &&
1774 rfc822_output_string (buf,"\015\012");
1777 /* Encode a body for 7BIT transmittal
1778 * Accepts: envelope
1779 * body
1782 void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
1784 void *f;
1785 PART *part;
1786 PARAMETER **param;
1787 if (body) switch (body->type) {
1788 case TYPEMULTIPART: /* multi-part */
1789 for (param = &body->parameter;
1790 *param && strcmp ((*param)->attribute,"BOUNDARY");
1791 param = &(*param)->next);
1792 if (!*param) { /* cookie not set up yet? */
1793 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
1794 sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1795 (unsigned long) random (),(unsigned long) time (0),
1796 (unsigned long) getpid ());
1797 (*param) = mail_newbody_parameter ();
1798 (*param)->attribute = cpystr ("BOUNDARY");
1799 (*param)->value = cpystr (tmp);
1801 part = body->nested.part; /* encode body parts */
1802 do rfc822_encode_body_7bit (env,&part->body);
1803 while ((part = part->next) != NULL); /* until done */
1804 break;
1805 case TYPEMESSAGE: /* encapsulated message */
1806 switch (body->encoding) {
1807 case ENC7BIT:
1808 break;
1809 case ENC8BIT:
1810 MM_LOG ("8-bit included message in 7-bit message body",PARSE);
1811 break;
1812 case ENCBINARY:
1813 MM_LOG ("Binary included message in 7-bit message body",PARSE);
1814 break;
1815 default:
1816 fatal ("Invalid rfc822_encode_body_7bit message encoding");
1818 break; /* can't change encoding */
1819 default: /* all else has some encoding */
1820 switch (body->encoding) {
1821 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
1822 /* remember old 8-bit contents */
1823 f = (void *) body->contents.text.data;
1824 body->contents.text.data =
1825 rfc822_8bit (body->contents.text.data,
1826 body->contents.text.size,&body->contents.text.size);
1827 body->encoding = ENCQUOTEDPRINTABLE;
1828 fs_give (&f); /* flush old binary contents */
1829 break;
1830 case ENCBINARY: /* encode binary into BASE64 */
1831 /* remember old binary contents */
1832 f = (void *) body->contents.text.data;
1833 body->contents.text.data =
1834 rfc822_binary ((void *) body->contents.text.data,
1835 body->contents.text.size,&body->contents.text.size);
1836 body->encoding = ENCBASE64;
1837 fs_give (&f); /* flush old binary contents */
1838 default: /* otherwise OK */
1839 break;
1841 break;
1845 /* Encode a body for 8BIT transmittal
1846 * Accepts: envelope
1847 * body
1850 void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
1852 void *f;
1853 PART *part;
1854 PARAMETER **param;
1855 if (body) switch (body->type) {
1856 case TYPEMULTIPART: /* multi-part */
1857 for (param = &body->parameter;
1858 *param && strcmp ((*param)->attribute,"BOUNDARY");
1859 param = &(*param)->next);
1860 if (!*param) { /* cookie not set up yet? */
1861 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
1862 sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1863 (unsigned long) random (),(unsigned long) time (0),
1864 (unsigned long) getpid ());
1865 (*param) = mail_newbody_parameter ();
1866 (*param)->attribute = cpystr ("BOUNDARY");
1867 (*param)->value = cpystr (tmp);
1869 part = body->nested.part; /* encode body parts */
1870 do rfc822_encode_body_8bit (env,&part->body);
1871 while ((part = part->next) != NULL); /* until done */
1872 break;
1873 case TYPEMESSAGE: /* encapsulated message */
1874 switch (body->encoding) {
1875 case ENC7BIT:
1876 case ENC8BIT:
1877 break;
1878 case ENCBINARY:
1879 MM_LOG ("Binary included message in 8-bit message body",PARSE);
1880 break;
1881 default:
1882 fatal ("Invalid rfc822_encode_body_7bit message encoding");
1884 break; /* can't change encoding */
1885 default: /* other type, encode binary into BASE64 */
1886 if (body->encoding == ENCBINARY) {
1887 /* remember old binary contents */
1888 f = (void *) body->contents.text.data;
1889 body->contents.text.data =
1890 rfc822_binary ((void *) body->contents.text.data,
1891 body->contents.text.size,&body->contents.text.size);
1892 body->encoding = ENCBASE64;
1893 fs_give (&f); /* flush old binary contents */
1895 break;
1899 /* Output RFC 822 text
1900 * Accepts: buffer
1901 * body
1902 * Returns: T if successful, NIL if failure
1905 long rfc822_output_text (RFC822BUFFER *buf,BODY *body)
1907 /* MULTIPART gets special handling */
1908 if (body->type == TYPEMULTIPART) {
1909 char *cookie,tmp[MAILTMPLEN];
1910 PARAMETER *param;
1911 PART *part;
1912 /* find cookie */
1913 for (param = body->parameter; param && strcmp (param->attribute,"BOUNDARY");
1914 param = param->next);
1915 if (param) cookie = param->value;
1916 else { /* make cookie not in BASE64 or QUOTEPRINT*/
1917 sprintf (cookie = tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1918 (unsigned long) random (),(unsigned long) time (0),
1919 (unsigned long) getpid ());
1920 (param = mail_newbody_parameter ())->attribute = cpystr ("BOUNDARY");
1921 param->value = cpystr (tmp);
1922 param->next = body->parameter;
1923 body->parameter = param;
1925 /* output each part */
1926 for (part = body->nested.part; part; part = part->next)
1927 if (!(rfc822_output_string (buf,"--") &&
1928 rfc822_output_string (buf,cookie) &&
1929 rfc822_output_string (buf,"\015\012") &&
1930 rfc822_output_body_header (buf,&part->body) &&
1931 rfc822_output_string (buf,"\015\012") &&
1932 rfc822_output_text (buf,&part->body))) return NIL;
1933 /* output trailing cookie */
1934 return rfc822_output_string (buf,"--") &&
1935 rfc822_output_string (buf,cookie) &&
1936 rfc822_output_string (buf,"--\015\012");
1938 /* output segment and trailing CRLF */
1939 return (!body->contents.text.data ||
1940 rfc822_output_string (buf,(char *) body->contents.text.data)) &&
1941 rfc822_output_string (buf,"\015\012");
1944 /* Body contents encoding/decoding routines */
1947 /* Convert BASE64 contents to binary
1948 * Accepts: source
1949 * length of source
1950 * pointer to return destination length
1951 * Returns: destination as binary or NIL if error
1954 #define WSP 0176 /* NUL, TAB, LF, FF, CR, SPC */
1955 #define JNK 0177
1956 #define PAD 0100
1958 void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
1960 char c,*s,tmp[MAILTMPLEN];
1961 void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1);
1962 char *d = (char *) ret;
1963 int e;
1964 static char decode[256] = {
1965 WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,WSP,WSP,JNK,WSP,WSP,JNK,JNK,
1966 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1967 WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,076,JNK,JNK,JNK,077,
1968 064,065,066,067,070,071,072,073,074,075,JNK,JNK,JNK,PAD,JNK,JNK,
1969 JNK,000,001,002,003,004,005,006,007,010,011,012,013,014,015,016,
1970 017,020,021,022,023,024,025,026,027,030,031,JNK,JNK,JNK,JNK,JNK,
1971 JNK,032,033,034,035,036,037,040,041,042,043,044,045,046,047,050,
1972 051,052,053,054,055,056,057,060,061,062,063,JNK,JNK,JNK,JNK,JNK,
1973 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1974 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1975 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1976 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1977 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1978 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1979 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1980 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK
1982 /* initialize block */
1983 memset (ret,0,((size_t) *len) + 1);
1984 *len = 0; /* in case we return an error */
1986 /* simple-minded decode */
1987 for (e = 0; srcl--; ) switch (c = decode[*src++]) {
1988 default: /* valid BASE64 data character */
1989 switch (e++) { /* install based on quantum position */
1990 case 0:
1991 *d = c << 2; /* byte 1: high 6 bits */
1992 break;
1993 case 1:
1994 *d++ |= c >> 4; /* byte 1: low 2 bits */
1995 *d = c << 4; /* byte 2: high 4 bits */
1996 break;
1997 case 2:
1998 *d++ |= c >> 2; /* byte 2: low 4 bits */
1999 *d = c << 6; /* byte 3: high 2 bits */
2000 break;
2001 case 3:
2002 *d++ |= c; /* byte 3: low 6 bits */
2003 e = 0; /* reinitialize mechanism */
2004 break;
2006 break;
2007 case WSP: /* whitespace */
2008 break;
2009 case PAD: /* padding */
2010 switch (e++) { /* check quantum position */
2011 case 3: /* one = is good enough in quantum 3 */
2012 /* make sure no data characters in remainder */
2013 for (; srcl; --srcl) switch (decode[*src++]) {
2014 /* ignore space, junk and extraneous padding */
2015 case WSP: case JNK: case PAD:
2016 break;
2017 default: /* valid BASE64 data character */
2018 /* This indicates bad MIME. One way that it can be caused is if
2019 a single-section message was BASE64 encoded and then something
2020 (e.g. a mailing list processor) appended text. The problem is
2021 that in 1 out of 3 cases, there is no padding and hence no way
2022 to detect the end of the data. Consequently, prudent software
2023 will always encapsulate a BASE64 segment inside a MULTIPART.
2025 sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s",
2026 (char *) src - 1);
2027 if ((s = strpbrk (tmp,"\015\012")) != NULL) *s = NIL;
2028 mm_log (tmp,PARSE);
2029 srcl = 1; /* don't issue any more messages */
2030 break;
2032 break;
2033 case 2: /* expect a second = in quantum 2 */
2034 if (srcl && (*src == '=')) break;
2035 default: /* impossible quantum position */
2036 fs_give (&ret);
2037 return NIL;
2039 break;
2040 case JNK: /* junk character */
2041 fs_give (&ret);
2042 return NIL;
2044 *len = d - (char *) ret; /* calculate data length */
2045 *d = '\0'; /* NUL terminate just in case */
2046 return ret; /* return the string */
2049 /* Convert binary contents to BASE64
2050 * Accepts: source
2051 * length of source
2052 * pointer to return destination length
2053 * Returns: destination as BASE64
2056 unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
2058 unsigned char *ret,*d;
2059 unsigned char *s = (unsigned char *) src;
2060 char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2061 unsigned long i = ((srcl + 2) / 3) * 4;
2062 *len = i += 2 * ((i / 60) + 1);
2063 d = ret = (unsigned char *) fs_get ((size_t) ++i);
2064 /* process tuplets */
2065 for (i = 0; srcl >= 3; s += 3, srcl -= 3) {
2066 *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
2067 /* byte 2: low 2 bits (1), high 4 bits (2) */
2068 *d++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f];
2069 /* byte 3: low 4 bits (2), high 2 bits (3) */
2070 *d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
2071 *d++ = v[s[2] & 0x3f]; /* byte 4: low 6 bits (3) */
2072 if ((++i) == 15) { /* output 60 characters? */
2073 i = 0; /* restart line break count, insert CRLF */
2074 *d++ = '\015'; *d++ = '\012';
2077 if (srcl) {
2078 *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
2079 /* byte 2: low 2 bits (1), high 4 bits (2) */
2080 *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
2081 /* byte 3: low 4 bits (2), high 2 bits (3) */
2082 *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
2083 /* byte 4: low 6 bits (3) */
2084 *d++ = srcl ? v[s[2] & 0x3f] : '=';
2085 if (srcl) srcl--; /* count third character if processed */
2086 if ((++i) == 15) { /* output 60 characters? */
2087 i = 0; /* restart line break count, insert CRLF */
2088 *d++ = '\015'; *d++ = '\012';
2091 *d++ = '\015'; *d++ = '\012'; /* insert final CRLF */
2092 *d = '\0'; /* tie off string */
2093 if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
2094 return ret; /* return the resulting string */
2097 /* Convert QUOTED-PRINTABLE contents to 8BIT
2098 * Accepts: source
2099 * length of source
2100 * pointer to return destination length
2101 * Returns: destination as 8-bit text or NIL if error
2104 unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
2105 unsigned long *len)
2107 char tmp[MAILTMPLEN];
2108 unsigned int bogon = NIL;
2109 unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
2110 unsigned char *d = ret;
2111 unsigned char *t = d;
2112 unsigned char *s = src;
2113 unsigned char c,e;
2114 *len = 0; /* in case we return an error */
2115 /* until run out of characters */
2116 while (((unsigned long) (s - src)) < srcl) {
2117 switch (c = *s++) { /* what type of character is it? */
2118 case '=': /* quoting character */
2119 if (((unsigned long) (s - src)) < srcl) switch (c = *s++) {
2120 case '\0': /* end of data */
2121 s--; /* back up pointer */
2122 break;
2123 case '\015': /* non-significant line break */
2124 if ((((unsigned long) (s - src)) < srcl) && (*s == '\012')) s++;
2125 case '\012': /* bare LF */
2126 t = d; /* accept any leading spaces */
2127 break;
2128 default: /* two hex digits then */
2129 if (isxdigit (c) && (((unsigned long) (s - src)) < srcl) &&
2130 isxdigit(*s)) {
2131 e = *s++; /* eat second hex digit now */
2132 *d++ = hex2byte (c,e);/* merge the two hex digits */
2134 else {
2135 /* This indicates bad MIME. One way that it can be caused is if
2136 a single-section message was QUOTED-PRINTABLE encoded and then
2137 something (e.g. a mailing list processor) appended text. The
2138 problem is that there is no way to determine where the encoded
2139 data ended and the appended crud began. Consequently, prudent
2140 software will always encapsulate a QUOTED-PRINTABLE segment
2141 inside a MULTIPART.
2143 if (!bogon++) { /* only do this once */
2144 sprintf (tmp,"Invalid quoted-printable sequence: =%.80s",
2145 (char *) s - 1);
2146 mm_log (tmp,PARSE);
2148 *d++ = '='; /* treat = as ordinary character */
2149 *d++ = c; /* and the character following */
2151 t = d; /* note point of non-space */
2152 break;
2154 break;
2155 case ' ': /* space, possibly bogus */
2156 *d++ = c; /* stash the space but don't update s */
2157 break;
2158 case '\015': /* end of line */
2159 case '\012': /* bare LF */
2160 d = t; /* slide back to last non-space, drop in */
2161 default:
2162 *d++ = c; /* stash the character */
2163 t = d; /* note point of non-space */
2166 *d = '\0'; /* tie off results */
2167 *len = d - ret; /* calculate length */
2168 return ret; /* return the string */
2171 /* Convert 8BIT contents to QUOTED-PRINTABLE
2172 * Accepts: source
2173 * length of source
2174 * pointer to return destination length
2175 * Returns: destination as quoted-printable text
2178 #define MAXL (size_t) 75 /* 76th position only used by continuation = */
2180 unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
2181 unsigned long *len)
2183 unsigned long lp = 0;
2184 unsigned char *ret = (unsigned char *)
2185 fs_get ((size_t) (3*srcl + 3*(((3*srcl)/MAXL) + 1)));
2186 unsigned char *d = ret;
2187 char *hex = "0123456789ABCDEF";
2188 unsigned char c;
2189 while (srcl--) { /* for each character */
2190 /* true line break? */
2191 if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
2192 *d++ = '\015'; *d++ = *src++; srcl--;
2193 lp = 0; /* reset line count */
2195 else { /* not a line break */
2196 /* quoting required? */
2197 if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
2198 ((c == ' ') && (*src == '\015'))) {
2199 if ((lp += 3) > MAXL) { /* yes, would line overflow? */
2200 *d++ = '='; *d++ = '\015'; *d++ = '\012';
2201 lp = 3; /* set line count */
2203 *d++ = '='; /* quote character */
2204 *d++ = hex[c >> 4]; /* high order 4 bits */
2205 *d++ = hex[c & 0xf]; /* low order 4 bits */
2207 else { /* ordinary character */
2208 if ((++lp) > MAXL) { /* would line overflow? */
2209 *d++ = '='; *d++ = '\015'; *d++ = '\012';
2210 lp = 1; /* set line count */
2212 *d++ = c; /* ordinary character */
2216 *d = '\0'; /* tie off destination */
2217 *len = d - ret; /* calculate true size */
2218 /* try to give some space back */
2219 fs_resize ((void **) &ret,(size_t) *len + 1);
2220 return ret;
2223 /* Legacy Routines */
2226 * WARNING: These routines are for compatibility with old software only.
2228 * Their use in new software is to be avoided.
2230 * These interfaces do not provide satisfactory buffer checking. In
2231 * versions of c-client prior to imap-2005, they did not provide any
2232 * buffer checking at all.
2234 * As a half-hearted attempt, these new compatability functions for the
2235 * legacy interfaces limit what they write to size SENDBUFLEN and will
2236 * fatal() if more than that is written. However, that isn't good enough
2237 * since several of these functions *append* to the buffer, and return an
2238 * updated pointer. Consequently, there is no way of knowing what the
2239 * actual available space is in the buffer, yet the function will still
2240 * write up to SENDBUFLEN bytes even if there is much less space actually
2241 * available. The result is a buffer overflow.
2243 * You won't get a buffer overflow if you never attempt to append using
2244 * these interfaces, but you can get the fatal() if it tries to write
2245 * more than SENDBUFLEN bytes.
2247 * To avoid this problem, use the corresponding rfc822_output_???()
2248 * functions instead, e.g., rfc822_output_address() instead of
2249 * rfc822_address().
2253 /* Flush routine, only called if overflow
2254 * Accepts: stream
2255 * string to output
2256 * Returns: never
2259 static long rfc822_legacy_soutr (void *stream,char *string)
2261 fatal ("rfc822.c legacy routine buffer overflow");
2262 return NIL;
2265 /* Legacy write RFC 2822 header from message structure
2266 * Accepts: scratch buffer to write into
2267 * message envelope
2268 * message body
2271 void rfc822_header (char *header,ENVELOPE *env,BODY *body)
2273 RFC822BUFFER buf;
2274 /* write at start of buffer */
2275 buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1;
2276 buf.f = rfc822_legacy_soutr;
2277 buf.s = NIL;
2278 rfc822_output_header (&buf,env,body,NIL,NIL);
2279 *buf.cur = '\0'; /* tie off buffer */
2283 /* Legacy write RFC 2822 text from header line
2284 * Accepts: pointer to destination string pointer
2285 * pointer to header type
2286 * message to interpret
2287 * pointer to text
2290 void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
2292 RFC822BUFFER buf;
2293 /* append to buffer */
2294 buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2295 buf.f = rfc822_legacy_soutr;
2296 buf.s = NIL;
2297 rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text);
2298 *(*header = buf.cur) = '\0'; /* tie off buffer */
2301 /* Legacy write RFC 2822 address from header line
2302 * Accepts: pointer to destination string pointer
2303 * pointer to header type
2304 * message to interpret
2305 * address to interpret
2308 void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
2310 RFC822BUFFER buf;
2311 /* append to buffer */
2312 buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2313 buf.f = rfc822_legacy_soutr;
2314 buf.s = NIL;
2315 rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL);
2316 *(*header = buf.cur) = '\0'; /* tie off buffer */
2320 /* Legacy write RFC 2822 address list
2321 * Accepts: pointer to destination string
2322 * address to interpret
2323 * header base if pretty-printing
2324 * Returns: end of destination string
2327 char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
2329 RFC822BUFFER buf;
2330 /* append to buffer */
2331 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2332 buf.f = rfc822_legacy_soutr;
2333 buf.s = NIL;
2334 rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL);
2335 *buf.cur = '\0'; /* tie off buffer */
2336 return buf.cur;
2340 /* Legacy write RFC 2822 route-address to string
2341 * Accepts: pointer to destination string
2342 * address to interpret
2345 void rfc822_address (char *dest,ADDRESS *adr)
2347 RFC822BUFFER buf;
2348 /* append to buffer */
2349 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2350 buf.f = rfc822_legacy_soutr;
2351 buf.s = NIL;
2352 rfc822_output_address (&buf,adr);
2353 *buf.cur = '\0'; /* tie off buffer */
2356 /* Concatenate RFC 2822 string
2357 * Accepts: pointer to destination string
2358 * pointer to string to concatenate
2359 * list of special characters or NIL for dot-atom format
2362 void rfc822_cat (char *dest,char *src,const char *specials)
2364 RFC822BUFFER buf;
2365 /* append to buffer */
2366 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2367 buf.f = rfc822_legacy_soutr;
2368 buf.s = NIL;
2369 rfc822_output_cat (&buf,src,specials);
2370 *buf.cur = '\0'; /* tie off buffer */
2374 /* Legacy write body content header
2375 * Accepts: pointer to destination string pointer
2376 * pointer to body to interpret
2379 void rfc822_write_body_header (char **dst,BODY *body)
2381 RFC822BUFFER buf;
2382 /* append to buffer */
2383 buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1;
2384 buf.f = rfc822_legacy_soutr;
2385 buf.s = NIL;
2386 rfc822_output_body_header (&buf,body);
2387 *(*dst = buf.cur) = '\0'; /* tie off buffer */
2390 /* Legacy output RFC 822 message
2391 * Accepts: temporary buffer
2392 * envelope
2393 * body
2394 * I/O routine
2395 * stream for I/O routine
2396 * non-zero if 8-bit output desired
2397 * Returns: T if successful, NIL if failure
2400 long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s,
2401 long ok8bit)
2403 long ret;
2404 rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
2405 /* call external RFC 2822 output generator */
2406 if (r822o) ret = (*r822o) (t,env,body,f,s,ok8bit);
2407 else { /* output generator not armed */
2408 RFC822BUFFER buf; /* use our own buffer rather than trust */
2409 char tmp[SENDBUFLEN+1]; /* client to give us a big enough one */
2410 buf.f = f;
2411 buf.s = s;
2412 buf.end = (buf.beg = buf.cur = t) + SENDBUFLEN - 1;
2413 tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
2414 ret = rfc822_output_full (&buf,env,body,ok8bit);
2416 return ret;
2420 /* Legacy output RFC 822 body
2421 * Accepts: body
2422 * I/O routine
2423 * stream for I/O routine
2424 * Returns: T if successful, NIL if failure
2427 long rfc822_output_body (BODY *body,soutr_t f,void *s)
2429 RFC822BUFFER buf;
2430 char tmp[SENDBUFLEN+1];
2431 buf.f = f;
2432 buf.s = s;
2433 buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
2434 tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
2435 return rfc822_output_text (&buf,body) && rfc822_output_flush (&buf);