imapext-2007

view src/c-client/rfc822.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children
line source
1 /* ========================================================================
2 * Copyright 1988-2008 University of Washington
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *
11 * ========================================================================
12 */
14 /*
15 * Program: RFC 2822 and MIME routines
16 *
17 * Author: Mark Crispin
18 * UW Technology
19 * University of Washington
20 * Seattle, WA 98195
21 * Internet: MRC@Washington.EDU
22 *
23 * Date: 27 July 1988
24 * Last Edited: 14 May 2008
25 *
26 * This original version of this file is
27 * Copyright 1988 Stanford University
28 * and was developed in the Symbolic Systems Resources Group of the Knowledge
29 * Systems Laboratory at Stanford University in 1987-88, and was funded by the
30 * Biomedical Research Technology Program of the NationalInstitutes of Health
31 * under grant number RR-00785.
32 */
35 #include <ctype.h>
36 #include <stdio.h>
37 #include <time.h>
38 #include "c-client.h"
41 /* Support for deprecated features in earlier specifications. Note that this
42 * module follows RFC 2822, and all use of "rfc822" in function names is
43 * for compatibility. Only the code identified by the conditionals below
44 * follows the earlier documents.
45 */
47 #define RFC733 1 /* parse "at" */
48 #define RFC822 0 /* generate A-D-L (MUST be 0 for 2822) */
50 /* RFC-822 static data */
52 #define RFC822CONT " " /* RFC 2822 continuation */
54 /* should have been "Remailed-" */
55 #define RESENTPREFIX "ReSent-"
56 static char *resentprefix = RESENTPREFIX;
57 /* syntax error host string */
58 static const char *errhst = ERRHOST;
61 /* Body formats constant strings, must match definitions in mail.h */
63 char *body_types[TYPEMAX+1] = {
64 "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
65 "MODEL", "X-UNKNOWN"
66 };
69 char *body_encodings[ENCMAX+1] = {
70 "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
71 };
74 /* Token delimiting special characters */
76 /* RFC 2822 specials */
77 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";
78 /* RFC 2822 phrase specials (no space) */
79 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";
80 /* RFC 2822 dot-atom specials (no dot) */
81 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";
82 /* RFC 2045 MIME body token specials */
83 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";
85 /* Subtype defaulting (a no-no, but regretably necessary...)
86 * Accepts: type code
87 * Returns: default subtype name
88 */
90 char *rfc822_default_subtype (unsigned short type)
91 {
92 switch (type) {
93 case TYPETEXT: /* default is TEXT/PLAIN */
94 return "PLAIN";
95 case TYPEMULTIPART: /* default is MULTIPART/MIXED */
96 return "MIXED";
97 case TYPEMESSAGE: /* default is MESSAGE/RFC822 */
98 return "RFC822";
99 case TYPEAPPLICATION: /* default is APPLICATION/OCTET-STREAM */
100 return "OCTET-STREAM";
101 case TYPEAUDIO: /* default is AUDIO/BASIC */
102 return "BASIC";
103 default: /* others have no default subtype */
104 return "UNKNOWN";
105 }
106 }
108 /* RFC 2822 parsing routines */
111 /* Parse an RFC 2822 message
112 * Accepts: pointer to return envelope
113 * pointer to return body
114 * pointer to header
115 * header byte count
116 * pointer to body stringstruct
117 * pointer to local host name
118 * recursion depth
119 * source driver flags
120 */
122 void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
123 STRING *bs,char *host,unsigned long depth,
124 unsigned long flags)
125 {
126 char c,*t,*d;
127 char *tmp = (char *) fs_get ((size_t) i + 100);
128 ENVELOPE *env = (*en = mail_newenvelope ());
129 BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
130 long MIMEp = -1; /* flag that MIME semantics are in effect */
131 parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL);
132 if (!host) host = BADHOST; /* make sure that host is non-null */
133 while (i && *s != '\n') { /* until end of header */
134 t = tmp; /* initialize buffer pointer */
135 c = ' '; /* and previous character */
136 while (i && c) { /* collect text until logical end of line */
137 switch (c = *s++) { /* slurp a character */
138 case '\015': /* return, possible end of logical line */
139 if (*s == '\n') break; /* ignore if LF follows */
140 case '\012': /* LF, possible end of logical line */
141 /* tie off unless next line starts with WS */
142 if (*s != ' ' && *s != '\t') *t++ = c = '\0';
143 break;
144 case '\t': /* tab */
145 *t++ = ' '; /* coerce to space */
146 break;
147 default: /* all other characters */
148 *t++ = c; /* insert the character into the line */
149 break;
150 }
151 if (!--i) *t++ = '\0'; /* see if end of header */
152 }
154 /* find header item type */
155 if (t = d = strchr (tmp,':')) {
156 *d++ = '\0'; /* tie off header item, point at its data */
157 while (*d == ' ') d++; /* flush whitespace */
158 while ((tmp < t--) && (*t == ' ')) *t = '\0';
159 ucase (tmp); /* coerce to uppercase */
160 /* external callback */
161 if (pl) (*pl) (env,tmp,d,host);
162 switch (*tmp) { /* dispatch based on first character */
163 case '>': /* possible >From: */
164 if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
165 break;
166 case 'B': /* possible bcc: */
167 if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
168 break;
169 case 'C': /* possible cc: or Content-<mumble>*/
170 if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
171 else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
172 (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
173 (tmp[7] == '-') && body)
174 switch (MIMEp) {
175 case -1: /* unknown if MIME or not */
176 if (!(MIMEp = /* see if MIME-Version header exists */
177 search ((unsigned char *) s-1,i,
178 (unsigned char *)"\012MIME-Version",(long) 13))) {
179 #if 1
180 /* This is a disgusting kludge, and most of the messages which
181 * benefit from it are spam.
182 */
183 if (!strcmp (tmp+8,"TRANSFER-ENCODING") ||
184 (!strcmp (tmp+8,"TYPE") && strchr (d,'/'))) {
185 MM_LOG ("Warning: MIME header encountered in non-MIME message",
186 PARSE);
187 MIMEp = 1; /* declare MIME now */
188 }
189 else
190 #endif
191 break; /* non-MIME message */
192 }
193 case T: /* definitely MIME */
194 rfc822_parse_content_header (body,tmp+8,d);
195 }
196 break;
197 case 'D': /* possible Date: */
198 if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
199 break;
200 case 'F': /* possible From: */
201 if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
202 else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
203 t = env->followup_to = (char *) fs_get (1 + strlen (d));
204 while (c = *d++) if (c != ' ') *t++ = c;
205 *t++ = '\0';
206 }
207 break;
208 case 'I': /* possible In-Reply-To: */
209 if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
210 env->in_reply_to = cpystr (d);
211 break;
213 case 'M': /* possible Message-ID: or MIME-Version: */
214 if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
215 env->message_id = cpystr (d);
216 else if (!strcmp (tmp+1,"IME-VERSION")) {
217 /* tie off at end of phrase */
218 if (t = rfc822_parse_phrase (d)) *t = '\0';
219 rfc822_skipws (&d); /* skip whitespace */
220 /* known version? */
221 if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
222 MM_LOG ("Warning: message has unknown MIME version",PARSE);
223 MIMEp = T; /* note that we are MIME */
224 }
225 break;
226 case 'N': /* possible Newsgroups: */
227 if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
228 t = env->newsgroups = (char *) fs_get (1 + strlen (d));
229 while (c = *d++) if (c != ' ') *t++ = c;
230 *t++ = '\0';
231 }
232 break;
233 case 'R': /* possible Reply-To: */
234 if (!strcmp (tmp+1,"EPLY-TO"))
235 rfc822_parse_adrlist (&env->reply_to,d,host);
236 else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
237 env->references = cpystr (d);
238 break;
239 case 'S': /* possible Subject: or Sender: */
240 if (!env->subject && !strcmp (tmp+1,"UBJECT"))
241 env->subject = cpystr (d);
242 else if (!strcmp (tmp+1,"ENDER"))
243 rfc822_parse_adrlist (&env->sender,d,host);
244 break;
245 case 'T': /* possible To: */
246 if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
247 break;
248 default:
249 break;
250 }
251 }
252 }
253 fs_give ((void **) &tmp); /* done with scratch buffer */
254 /* default Sender: and Reply-To: to From: */
255 if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
256 if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
257 /* now parse the body */
258 if (body) rfc822_parse_content (body,bs,host,depth,flags);
259 }
261 /* Parse a message body content
262 * Accepts: pointer to body structure
263 * body string
264 * pointer to local host name
265 * recursion depth
266 * source driver flags
267 */
269 void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
270 unsigned long flags)
271 {
272 char c,c1,*s,*s1;
273 int f;
274 unsigned long i,j,k,m;
275 PARAMETER *param;
276 PART *part = NIL;
277 if (depth > MAXMIMEDEPTH) { /* excessively deep recursion? */
278 body->type = TYPETEXT; /* yes, probably a malicious MIMEgram */
279 MM_LOG ("Ignoring excessively deep MIME recursion",PARSE);
280 }
281 if (!body->subtype) /* default subtype if still unknown */
282 body->subtype = cpystr (rfc822_default_subtype (body->type));
283 /* note offset and sizes */
284 body->contents.offset = GETPOS (bs);
285 /* note internal body size in all cases */
286 body->size.bytes = body->contents.text.size = i = SIZE (bs);
287 if (!(flags & DR_CRLF)) body->size.bytes = strcrlflen (bs);
288 switch (body->type) { /* see if anything else special to do */
289 case TYPETEXT: /* text content */
290 if (!body->parameter) { /* no parameters set */
291 body->parameter = mail_newbody_parameter ();
292 body->parameter->attribute = cpystr ("CHARSET");
293 while (i--) { /* count lines and guess charset */
294 c = SNX (bs); /* get current character */
295 /* charset still unknown? */
296 if (!body->parameter->value) {
297 if ((c == I2C_ESC) && (i && i--) && ((c = SNX (bs)) == I2C_MULTI) &&
298 (i && i--) && (((c = SNX (bs)) == I2CS_94x94_JIS_NEW) ||
299 (c == I2CS_94x94_JIS_OLD)))
300 body->parameter->value = cpystr ("ISO-2022-JP");
301 else if (c & 0x80) body->parameter->value = cpystr ("X-UNKNOWN");
302 }
303 if (c == '\n') body->size.lines++;
304 }
305 /* 7-bit content */
306 if (!body->parameter->value) switch (body->encoding) {
307 case ENC7BIT: /* unadorned 7-bit */
308 case ENC8BIT: /* unadorned 8-bit (but 7-bit content) */
309 case ENCBINARY: /* binary (but 7-bit content( */
310 body->parameter->value = cpystr ("US-ASCII");
311 break;
312 default: /* QUOTED-PRINTABLE, BASE64, etc. */
313 body->parameter->value = cpystr ("X-UNKNOWN");
314 break;
315 }
316 }
317 /* just count lines */
318 else while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
319 break;
321 case TYPEMESSAGE: /* encapsulated message */
322 /* encapsulated RFC-822 message? */
323 if (!strcmp (body->subtype,"RFC822")) {
324 body->nested.msg = mail_newmsg ();
325 switch (body->encoding) { /* make sure valid encoding */
326 case ENC7BIT: /* these are valid nested encodings */
327 case ENC8BIT:
328 case ENCBINARY:
329 break;
330 default:
331 MM_LOG ("Ignoring nested encoding of message contents",PARSE);
332 }
333 /* hunt for blank line */
334 for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
335 j++) if ((c1 = SNX (bs)) != '\015') c = c1;
336 if (i > j) { /* unless no more text */
337 c1 = SNX (bs); /* body starts here */
338 j++; /* advance count */
339 }
340 /* note body text offset and header size */
341 body->nested.msg->header.text.size = j;
342 body->nested.msg->text.text.size = body->contents.text.size - j;
343 body->nested.msg->text.offset = GETPOS (bs);
344 body->nested.msg->full.offset = body->nested.msg->header.offset =
345 body->contents.offset;
346 body->nested.msg->full.text.size = body->contents.text.size;
347 /* copy header string */
348 SETPOS (bs,body->contents.offset);
349 s = (char *) fs_get ((size_t) j + 1);
350 for (s1 = s,k = j; k--; *s1++ = SNX (bs));
351 s[j] = '\0'; /* tie off string (not really necessary) */
352 /* now parse the body */
353 rfc822_parse_msg_full (&body->nested.msg->env,&body->nested.msg->body,s,
354 j,bs,h,depth+1,flags);
355 fs_give ((void **) &s); /* free header string */
356 /* restore position */
357 SETPOS (bs,body->contents.offset);
358 }
359 /* count number of lines */
360 while (i--) if (SNX (bs) == '\n') body->size.lines++;
361 break;
362 case TYPEMULTIPART: /* multiple parts */
363 switch (body->encoding) { /* make sure valid encoding */
364 case ENC7BIT: /* these are valid nested encodings */
365 case ENC8BIT:
366 case ENCBINARY:
367 break;
368 default:
369 MM_LOG ("Ignoring nested encoding of multipart contents",PARSE);
370 }
371 /* remember if digest */
372 f = !strcmp (body->subtype,"DIGEST");
373 /* find cookie */
374 for (s1 = NIL,param = body->parameter; param && !s1; param = param->next)
375 if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value;
376 if (!s1) s1 = "-"; /* yucky default */
377 j = strlen (s1) + 2; /* length of cookie and header */
378 c = '\012'; /* initially at beginning of line */
380 while (i > j) { /* examine data */
381 if (m = GETPOS (bs)) m--; /* get position in front of character */
382 switch (c) { /* examine each line */
383 case '\015': /* handle CRLF form */
384 if (CHR (bs) == '\012'){/* following LF? */
385 c = SNX (bs); i--; /* yes, slurp it */
386 }
387 case '\012': /* at start of a line, start with -- ? */
388 if (!(i && i-- && ((c = SNX (bs)) == '-') && i-- &&
389 ((c = SNX (bs)) == '-'))) break;
390 /* see if cookie matches */
391 if (k = j - 2) for (s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
392 if (k) break; /* strings didn't match if non-zero */
393 /* terminating delimiter? */
394 if ((c = ((i && i--) ? (SNX (bs)) : '\012')) == '-') {
395 if ((i && i--) && ((c = SNX (bs)) == '-') &&
396 ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) {
397 /* if have a final part calculate its size */
398 if (part) part->body.mime.text.size =
399 (m > part->body.mime.offset) ? (m - part->body.mime.offset) :0;
400 part = NIL; i = 1; /* terminate scan */
401 }
402 break;
403 }
404 /* swallow trailing whitespace */
405 while ((c == ' ') || (c == '\t'))
406 c = ((i && i--) ? (SNX (bs)) : '\012');
407 switch (c) { /* need newline after all of it */
408 case '\015': /* handle CRLF form */
409 if (i && CHR (bs) == '\012') {
410 c = SNX (bs); i--;/* yes, slurp it */
411 }
412 case '\012': /* new line */
413 if (part) { /* calculate size of previous */
414 part->body.mime.text.size =
415 (m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0;
416 /* instantiate next */
417 part = part->next = mail_newbody_part ();
418 } /* otherwise start new list */
419 else part = body->nested.part = mail_newbody_part ();
420 /* digest has a different default */
421 if (f) part->body.type = TYPEMESSAGE;
422 /* note offset from main body */
423 part->body.mime.offset = GETPOS (bs);
424 break;
425 default: /* whatever it was it wasn't valid */
426 break;
427 }
428 break;
429 default: /* not at a line */
430 c = SNX (bs); i--; /* get next character */
431 break;
432 }
433 }
435 /* calculate size of any final part */
436 if (part) part->body.mime.text.size = i +
437 ((GETPOS(bs) > part->body.mime.offset) ?
438 (GETPOS(bs) - part->body.mime.offset) : 0);
439 /* make a scratch buffer */
440 s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN));
441 /* in case empty multipart */
442 if (!body->nested.part) body->nested.part = mail_newbody_part ();
443 /* parse non-empty body parts */
444 for (part = body->nested.part; part; part = part->next) {
445 /* part non-empty (header and/or content)? */
446 if (i = part->body.mime.text.size) {
447 /* move to that part of the body */
448 SETPOS (bs,part->body.mime.offset);
449 /* until end of header */
450 while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) {
451 /* collect text until logical end of line */
452 for (j = 0,c = ' '; c; ) {
453 /* make sure buffer big enough */
454 if (j > (k - 10)) fs_resize ((void **) &s1,k += MAILTMPLEN);
455 switch (c1 = SNX (bs)) {
456 case '\015': /* return */
457 if (i && (CHR (bs) == '\012')) {
458 c1 = SNX (bs); /* eat any LF following */
459 i--;
460 }
461 case '\012': /* newline, possible end of logical line */
462 /* tie off unless continuation */
463 if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
464 s1[j] = c = '\0';
465 break;
466 case '\t': /* tab */
467 case ' ': /* insert whitespace if not already there */
468 if (c != ' ') s1[j++] = c = ' ';
469 break;
470 default: /* all other characters */
471 s1[j++] = c = c1; /* insert the character into the line */
472 break;
473 }
474 /* end of data ties off the header */
475 if (!i || !--i) s1[j++] = c = '\0';
476 }
478 /* find header item type */
479 if (((s1[0] == 'C') || (s1[0] == 'c')) &&
480 ((s1[1] == 'O') || (s1[1] == 'o')) &&
481 ((s1[2] == 'N') || (s1[2] == 'n')) &&
482 ((s1[3] == 'T') || (s1[3] == 't')) &&
483 ((s1[4] == 'E') || (s1[4] == 'e')) &&
484 ((s1[5] == 'N') || (s1[5] == 'n')) &&
485 ((s1[6] == 'T') || (s1[6] == 't')) &&
486 (s1[7] == '-') && (s = strchr (s1+8,':'))) {
487 /* tie off and flush whitespace */
488 for (*s++ = '\0'; *s == ' '; s++);
489 /* parse the header */
490 rfc822_parse_content_header (&part->body,ucase (s1+8),s);
491 }
492 }
493 /* skip header trailing (CR)LF */
494 if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
495 if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
496 j = bs->size; /* save upper level size */
497 /* set offset for next level, fake size to i */
498 bs->size = GETPOS (bs) + i;
499 part->body.mime.text.size -= i;
500 /* now parse it */
501 rfc822_parse_content (&part->body,bs,h,depth+1,flags);
502 bs->size = j; /* restore current level size */
503 }
504 else { /* zero-length part, use default subtype */
505 part->body.subtype = cpystr (rfc822_default_subtype (part->body.type));
506 /* see if anything else special to do */
507 switch (part->body.type) {
508 case TYPETEXT: /* text content */
509 /* default parameters */
510 if (!part->body.parameter) {
511 part->body.parameter = mail_newbody_parameter ();
512 part->body.parameter->attribute = cpystr ("CHARSET");
513 /* only assume US-ASCII if 7BIT */
514 part->body.parameter->value =
515 cpystr ((part->body.encoding == ENC7BIT) ?
516 "US-ASCII" : "X-UNKNOWN");
517 }
518 break;
519 case TYPEMESSAGE: /* encapsulated message in digest */
520 part->body.nested.msg = mail_newmsg ();
521 break;
522 default:
523 break;
524 }
525 }
526 }
527 fs_give ((void **) &s1); /* finished with scratch buffer */
528 break;
529 default: /* nothing special to do in any other case */
530 break;
531 }
532 }
534 /* Parse RFC 2822 body content header
535 * Accepts: body to write to
536 * possible content name
537 * remainder of header
538 */
540 void rfc822_parse_content_header (BODY *body,char *name,char *s)
541 {
542 char c,*t,tmp[MAILTMPLEN];
543 long i;
544 STRINGLIST *stl;
545 rfc822_skipws (&s); /* skip leading comments */
546 /* flush whitespace */
547 if (t = strchr (name,' ')) *t = '\0';
548 switch (*name) { /* see what kind of content */
549 case 'I': /* possible Content-ID */
550 if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
551 break;
552 case 'D': /* possible Content-Description */
553 if (!(strcmp (name+1,"ESCRIPTION") || body->description))
554 body->description = cpystr (s);
555 if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) {
556 /* get type word */
557 if (!(name = rfc822_parse_word (s,tspecials))) break;
558 c = *name; /* remember delimiter */
559 *name = '\0'; /* tie off type */
560 body->disposition.type = ucase (cpystr (s));
561 *name = c; /* restore delimiter */
562 rfc822_skipws (&name); /* skip whitespace */
563 rfc822_parse_parameter (&body->disposition.parameter,name);
564 }
565 break;
566 case 'L': /* possible Content-Language */
567 if (!(strcmp (name+1,"ANGUAGE") || body->language)) {
568 stl = NIL; /* process languages */
569 while (s && (name = rfc822_parse_word (s,tspecials))) {
570 c = *name; /* save delimiter */
571 *name = '\0'; /* tie off subtype */
572 if (stl) stl = stl->next = mail_newstringlist ();
573 else stl = body->language = mail_newstringlist ();
574 stl->text.data = (unsigned char *) ucase (cpystr (s));
575 stl->text.size = strlen ((char *) stl->text.data);
576 *name = c; /* restore delimiter */
577 rfc822_skipws (&name); /* skip whitespace */
578 if (*name == ',') { /* any more languages? */
579 s = ++name; /* advance to it them */
580 rfc822_skipws (&s);
581 }
582 else s = NIL; /* bogus or end of list */
583 }
584 }
585 else if (!(strcmp (name+1,"OCATION") || body->location))
586 body->location = cpystr (s);
587 break;
588 case 'M': /* possible Content-MD5 */
589 if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
590 break;
592 case 'T': /* possible Content-Type/Transfer-Encoding */
593 if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) {
594 /* get type word */
595 if (!(name = rfc822_parse_word (s,tspecials))) break;
596 c = *name; /* remember delimiter */
597 *name = '\0'; /* tie off type */
598 /* search for body type */
599 for (i = 0,s = rfc822_cpy (s);
600 (i <= TYPEMAX) && body_types[i] &&
601 compare_cstring (s,body_types[i]); i++);
602 if (i > TYPEMAX) { /* fell off end of loop? */
603 body->type = TYPEOTHER; /* coerce to X-UNKNOWN */
604 sprintf (tmp,"MIME type table overflow: %.100s",s);
605 MM_LOG (tmp,PARSE);
606 }
607 else { /* record body type index */
608 body->type = (unsigned short) i;
609 /* and name if new type */
610 if (body_types[body->type]) fs_give ((void **) &s);
611 else { /* major MIME body type unknown to us */
612 body_types[body->type] = ucase (s);
613 sprintf (tmp,"Unknown MIME type: %.100s",s);
614 MM_LOG (tmp,PARSE);
615 }
616 }
617 *name = c; /* restore delimiter */
618 rfc822_skipws (&name); /* skip whitespace */
619 if ((*name == '/') && /* subtype? */
620 (name = rfc822_parse_word ((s = ++name),tspecials))) {
621 c = *name; /* save delimiter */
622 *name = '\0'; /* tie off subtype */
623 rfc822_skipws (&s); /* copy subtype */
624 if (s) body->subtype = ucase (rfc822_cpy (s));
625 *name = c; /* restore delimiter */
626 rfc822_skipws (&name); /* skip whitespace */
627 }
628 else if (!name) { /* no subtype, was a subtype delimiter? */
629 name = s; /* barf, restore pointer */
630 rfc822_skipws (&name); /* skip leading whitespace */
631 }
632 rfc822_parse_parameter (&body->parameter,name);
633 }
635 else if (!strcmp (name+1,"RANSFER-ENCODING")) {
636 if (!(name = rfc822_parse_word (s,tspecials))) break;
637 c = *name; /* remember delimiter */
638 *name = '\0'; /* tie off encoding */
639 /* search for body encoding */
640 for (i = 0,s = rfc822_cpy (s);
641 (i <= ENCMAX) && body_encodings[i] &&
642 compare_cstring (s,body_encodings[i]); i++);
643 if (i > ENCMAX) { /* fell off end of loop? */
644 body->encoding = ENCOTHER;
645 sprintf (tmp,"MIME encoding table overflow: %.100s",s);
646 MM_LOG (tmp,PARSE);
647 }
648 else { /* record body encoding index */
649 body->encoding = (unsigned short) i;
650 /* and name if new encoding */
651 if (body_encodings[body->encoding]) fs_give ((void **) &s);
652 else {
653 body_encodings[body->encoding] = ucase (s);
654 sprintf (tmp,"Unknown MIME transfer encoding: %.100s",s);
655 MM_LOG (tmp,PARSE);
656 }
657 }
658 *name = c; /* restore delimiter */
659 /* ??check for cruft here?? */
660 }
661 break;
662 default: /* otherwise unknown */
663 break;
664 }
665 }
667 /* Parse RFC 2822 body parameter list
668 * Accepts: parameter list to write to
669 * text of list
670 */
672 void rfc822_parse_parameter (PARAMETER **par,char *text)
673 {
674 char c,*s,tmp[MAILTMPLEN];
675 PARAMETER *param = NIL;
676 /* parameter list? */
677 while (text && (*text == ';') &&
678 (text = rfc822_parse_word ((s = ++text),tspecials))) {
679 c = *text; /* remember delimiter */
680 *text = '\0'; /* tie off attribute name */
681 rfc822_skipws (&s); /* skip leading attribute whitespace */
682 if (!*s) *text = c; /* must have an attribute name */
683 else { /* instantiate a new parameter */
684 if (*par) param = param->next = mail_newbody_parameter ();
685 else param = *par = mail_newbody_parameter ();
686 param->attribute = ucase (cpystr (s));
687 *text = c; /* restore delimiter */
688 rfc822_skipws (&text); /* skip whitespace before equal sign */
689 if ((*text == '=') && /* make sure have value */
690 (text = rfc822_parse_word ((s = ++text),tspecials))) {
691 c = *text; /* remember delimiter */
692 *text = '\0'; /* tie off value */
693 rfc822_skipws (&s); /* skip leading value whitespace */
694 if (*s) param->value = rfc822_cpy (s);
695 *text = c; /* restore delimiter */
696 rfc822_skipws (&text);
697 }
698 if (!param->value) { /* value not found? */
699 param->value = cpystr ("MISSING_PARAMETER_VALUE");
700 sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
701 MM_LOG (tmp,PARSE);
702 }
703 }
704 }
705 /* string not present */
706 if (!text) MM_LOG ("Missing parameter",PARSE);
707 else if (*text) { /* must be end of poop */
708 sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
709 MM_LOG (tmp,PARSE);
710 }
711 }
713 /* Parse RFC 2822 address list
714 * Accepts: address list to write to
715 * input string
716 * default host name
717 */
719 void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
720 {
721 int c;
722 char *s,tmp[MAILTMPLEN];
723 ADDRESS *last = *lst;
724 ADDRESS *adr;
725 if (!string) return; /* no string */
726 rfc822_skipws (&string); /* skip leading WS */
727 if (!*string) return; /* empty string */
728 /* run to tail of list */
729 if (last) while (last->next) last = last->next;
730 while (string) { /* loop until string exhausted */
731 while (*string == ',') { /* RFC 822 allowed null addresses!! */
732 ++string; /* skip the comma */
733 rfc822_skipws (&string); /* and any leading WS */
734 }
735 if (!*string) string = NIL; /* punt if ran out of string */
736 /* got an address? */
737 else if (adr = rfc822_parse_address (lst,last,&string,host,0)) {
738 last = adr; /* new tail address */
739 if (string) { /* analyze what follows */
740 rfc822_skipws (&string);
741 switch (c = *(unsigned char *) string) {
742 case ',': /* comma? */
743 ++string; /* then another address follows */
744 break;
745 default:
746 s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
747 "Unexpected characters at end of address: %.80s";
748 sprintf (tmp,s,string);
749 MM_LOG (tmp,PARSE);
750 last = last->next = mail_newaddr ();
751 last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
752 last->host = cpystr (errhst);
753 /* falls through */
754 case '\0': /* null-specified address? */
755 string = NIL; /* punt remainder of parse */
756 break;
757 }
758 }
759 }
760 else if (string) { /* bad mailbox */
761 rfc822_skipws (&string); /* skip WS */
762 if (!*string) strcpy (tmp,"Missing address after comma");
763 else sprintf (tmp,"Invalid mailbox list: %.80s",string);
764 MM_LOG (tmp,PARSE);
765 string = NIL;
766 (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
767 adr->host = cpystr (errhst);
768 if (last) last = last->next = adr;
769 else *lst = last = adr;
770 break;
771 }
772 }
773 }
775 /* Parse RFC 2822 address
776 * Accepts: address list to write to
777 * tail of address list
778 * pointer to input string
779 * default host name
780 * group nesting depth
781 * Returns: new list tail
782 */
784 ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
785 char *defaulthost,unsigned long depth)
786 {
787 ADDRESS *adr;
788 if (!*string) return NIL; /* no string */
789 rfc822_skipws (string); /* skip leading WS */
790 if (!**string) return NIL; /* empty string */
791 if (adr = rfc822_parse_group (lst,last,string,defaulthost,depth)) last = adr;
792 /* got an address? */
793 else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
794 if (!*lst) *lst = adr; /* yes, first time through? */
795 else last->next = adr; /* no, append to the list */
796 /* set for subsequent linking */
797 for (last = adr; last->next; last = last->next);
798 }
799 else if (*string) return NIL;
800 return last;
801 }
803 /* Parse RFC 2822 group
804 * Accepts: address list to write to
805 * pointer to tail of address list
806 * pointer to input string
807 * default host name
808 * group nesting depth
809 */
811 ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
812 char *defaulthost,unsigned long depth)
813 {
814 char tmp[MAILTMPLEN];
815 char *p,*s;
816 ADDRESS *adr;
817 if (depth > MAXGROUPDEPTH) { /* excessively deep recursion? */
818 MM_LOG ("Ignoring excessively deep group recursion",PARSE);
819 return NIL; /* probably abusive */
820 }
821 if (!*string) return NIL; /* no string */
822 rfc822_skipws (string); /* skip leading WS */
823 if (!**string || /* trailing whitespace or not group */
824 ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
825 return NIL;
826 s = p; /* end of candidate phrase */
827 rfc822_skipws (&s); /* find delimiter */
828 if (*s != ':') return NIL; /* not really a group */
829 *p = '\0'; /* tie off group name */
830 p = ++s; /* continue after the delimiter */
831 rfc822_skipws (&p); /* skip subsequent whitespace */
832 /* write as address */
833 (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
834 if (!*lst) *lst = adr; /* first time through? */
835 else last->next = adr; /* no, append to the list */
836 last = adr; /* set for subsequent linking */
837 *string = p; /* continue after this point */
838 while (*string && **string && (**string != ';')) {
839 if (adr = rfc822_parse_address (lst,last,string,defaulthost,depth+1)) {
840 last = adr; /* new tail address */
841 if (*string) { /* anything more? */
842 rfc822_skipws (string); /* skip whitespace */
843 switch (**string) { /* see what follows */
844 case ',': /* another address? */
845 ++*string; /* yes, skip past the comma */
846 case ';': /* end of group? */
847 case '\0': /* end of string */
848 break;
849 default:
850 sprintf (tmp,"Unexpected characters after address in group: %.80s",
851 *string);
852 MM_LOG (tmp,PARSE);
853 *string = NIL; /* cancel remainder of parse */
854 last = last->next = mail_newaddr ();
855 last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
856 last->host = cpystr (errhst);
857 }
858 }
859 }
860 else { /* bogon */
861 sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
862 MM_LOG (tmp,PARSE);
863 *string = NIL; /* cancel remainder of parse */
864 (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
865 adr->host = cpystr (errhst);
866 last = last->next = adr;
867 }
868 }
869 if (*string) { /* skip close delimiter */
870 if (**string == ';') ++*string;
871 rfc822_skipws (string);
872 }
873 /* append end of address mark to the list */
874 last->next = (adr = mail_newaddr ());
875 last = adr; /* set for subsequent linking */
876 return last; /* return the tail */
877 }
879 /* Parse RFC 2822 mailbox
880 * Accepts: pointer to string pointer
881 * default host
882 * Returns: address list
883 *
884 * Updates string pointer
885 */
887 ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
888 {
889 ADDRESS *adr = NIL;
890 char *s,*end;
891 parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL);
892 if (!*string) return NIL; /* no string */
893 rfc822_skipws (string); /* flush leading whitespace */
894 if (!**string) return NIL; /* empty string */
895 if (*(s = *string) == '<') /* note start, handle case of phraseless RA */
896 adr = rfc822_parse_routeaddr (s,string,defaulthost);
897 /* otherwise, expect at least one word */
898 else if (end = rfc822_parse_phrase (s)) {
899 if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) {
900 /* phrase is a personal name */
901 if (adr->personal) fs_give ((void **) &adr->personal);
902 *end = '\0'; /* tie off phrase */
903 adr->personal = rfc822_cpy (s);
904 }
905 /* call external phraseparser if phrase only */
906 else if (pp && rfc822_phraseonly (end) &&
907 (adr = (*pp) (s,end,defaulthost))) {
908 *string = end; /* update parse pointer */
909 rfc822_skipws (string); /* skip WS in the normal way */
910 }
911 else adr = rfc822_parse_addrspec (s,string,defaulthost);
912 }
913 return adr; /* return the address */
914 }
917 /* Check if address is a phrase only
918 * Accepts: pointer to end of phrase
919 * Returns: T if phrase only, else NIL;
920 */
922 long rfc822_phraseonly (char *end)
923 {
924 while (*end == ' ') ++end; /* call rfc822_skipws() instead?? */
925 switch (*end) {
926 case '\0': case ',': case ';':
927 return LONGT; /* is a phrase only */
928 }
929 return NIL; /* something other than phase is here */
930 }
932 /* Parse RFC 2822 route-address
933 * Accepts: string pointer
934 * pointer to string pointer to update
935 * Returns: address
936 *
937 * Updates string pointer
938 */
940 ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
941 {
942 char tmp[MAILTMPLEN];
943 ADDRESS *adr;
944 char *s,*t,*adl;
945 size_t adllen,i;
946 if (!string) return NIL;
947 rfc822_skipws (&string); /* flush leading whitespace */
948 /* must start with open broket */
949 if (*string != '<') return NIL;
950 t = ++string; /* see if A-D-L there */
951 rfc822_skipws (&t); /* flush leading whitespace */
952 for (adl = NIL,adllen = 0; /* parse possible A-D-L */
953 (*t == '@') && (s = rfc822_parse_domain (t+1,&t));) {
954 i = strlen (s) + 2; /* @ plus domain plus delimiter or NUL */
955 if (adl) { /* have existing A-D-L? */
956 fs_resize ((void **) &adl,adllen + i);
957 sprintf (adl + adllen - 1,",@%s",s);
958 }
959 /* write initial A-D-L */
960 else sprintf (adl = (char *) fs_get (i),"@%s",s);
961 adllen += i; /* new A-D-L length */
962 fs_give ((void **) &s); /* don't need domain any more */
963 rfc822_skipws (&t); /* skip WS */
964 if (*t != ',') break; /* put if not comma */
965 t++; /* skip the comma */
966 rfc822_skipws (&t); /* skip WS */
967 }
968 if (adl) { /* got an A-D-L? */
969 if (*t != ':') { /* make sure syntax good */
970 sprintf (tmp,"Unterminated at-domain-list: %.80s%.80s",adl,t);
971 MM_LOG (tmp,PARSE);
972 }
973 else string = ++t; /* continue parse from this point */
974 }
976 /* parse address spec */
977 if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) {
978 if (adl) fs_give ((void **) &adl);
979 return NIL;
980 }
981 if (adl) adr->adl = adl; /* have an A-D-L? */
982 if (*ret) if (**ret == '>') { /* make sure terminated OK */
983 ++*ret; /* skip past the broket */
984 rfc822_skipws (ret); /* flush trailing WS */
985 if (!**ret) *ret = NIL; /* wipe pointer if at end of string */
986 return adr; /* return the address */
987 }
988 sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
989 *adr->host == '@' ? "<null>" : adr->host);
990 MM_LOG (tmp,PARSE);
991 adr->next = mail_newaddr ();
992 adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
993 adr->next->host = cpystr (errhst);
994 return adr; /* return the address */
995 }
997 /* Parse RFC 2822 address-spec
998 * Accepts: string pointer
999 * pointer to string pointer to update
1000 * default host
1001 * Returns: address
1003 * Updates string pointer
1004 */
1006 ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
1008 ADDRESS *adr;
1009 char c,*s,*t,*v,*end;
1010 if (!string) return NIL; /* no string */
1011 rfc822_skipws (&string); /* flush leading whitespace */
1012 if (!*string) return NIL; /* empty string */
1013 /* find end of mailbox */
1014 if (!(t = rfc822_parse_word (string,wspecials))) return NIL;
1015 adr = mail_newaddr (); /* create address block */
1016 c = *t; /* remember delimiter */
1017 *t = '\0'; /* tie off mailbox */
1018 /* copy mailbox */
1019 adr->mailbox = rfc822_cpy (string);
1020 *t = c; /* restore delimiter */
1021 end = t; /* remember end of mailbox */
1022 rfc822_skipws (&t); /* skip whitespace */
1023 while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
1024 string = ++t; /* skip past the dot and any WS */
1025 rfc822_skipws (&string);
1026 /* get next word of mailbox */
1027 if (t = rfc822_parse_word (string,wspecials)) {
1028 end = t; /* remember new end of mailbox */
1029 c = *t; /* remember delimiter */
1030 *t = '\0'; /* tie off word */
1031 s = rfc822_cpy (string); /* copy successor part */
1032 *t = c; /* restore delimiter */
1033 /* build new mailbox */
1034 sprintf (v = (char *) fs_get (strlen (adr->mailbox) + strlen (s) + 2),
1035 "%s.%s",adr->mailbox,s);
1036 fs_give ((void **) &adr->mailbox);
1037 adr->mailbox = v; /* new host name */
1038 rfc822_skipws (&t); /* skip WS after mailbox */
1040 else { /* barf */
1041 MM_LOG ("Invalid mailbox part after .",PARSE);
1042 break;
1045 t = end; /* remember delimiter in case no host */
1047 rfc822_skipws (&end); /* sniff ahead at what follows */
1048 #if RFC733 /* RFC 733 used "at" instead of "@" */
1049 if (((*end == 'a') || (*end == 'A')) &&
1050 ((end[1] == 't') || (end[1] == 'T')) &&
1051 ((end[2] == ' ') || (end[2] == '\t') || (end[2] == '\015') ||
1052 (end[2] == '\012') || (end[2] == '(')))
1053 *++end = '@';
1054 #endif
1055 if (*end != '@') end = t; /* host name missing */
1056 /* otherwise parse host name */
1057 else if (!(adr->host = rfc822_parse_domain (++end,&end)))
1058 adr->host = cpystr (errhst);
1059 /* default host if missing */
1060 if (!adr->host) adr->host = cpystr (defaulthost);
1061 /* try person name in comments if missing */
1062 if (end && !(adr->personal && *adr->personal)) {
1063 while (*end == ' ') ++end; /* see if we can find a person name here */
1064 if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
1065 adr->personal = rfc822_cpy (s);
1066 rfc822_skipws (&end); /* skip any other WS in the normal way */
1068 /* set return to end pointer */
1069 *ret = (end && *end) ? end : NIL;
1070 return adr; /* return the address we got */
1073 /* Parse RFC 2822 domain
1074 * Accepts: string pointer
1075 * pointer to return end of domain
1076 * Returns: domain name or NIL if failure
1077 */
1079 char *rfc822_parse_domain (char *string,char **end)
1081 char *ret = NIL;
1082 char c,*s,*t,*v;
1083 rfc822_skipws (&string); /* skip whitespace */
1084 if (*string == '[') { /* domain literal? */
1085 if (!(*end = rfc822_parse_word (string + 1,"]\\")))
1086 MM_LOG ("Empty domain literal",PARSE);
1087 else if (**end != ']') MM_LOG ("Unterminated domain literal",PARSE);
1088 else {
1089 size_t len = ++*end - string;
1090 strncpy (ret = (char *) fs_get (len + 1),string,len);
1091 ret[len] = '\0'; /* tie off literal */
1094 /* search for end of host */
1095 else if (t = rfc822_parse_word (string,wspecials)) {
1096 c = *t; /* remember delimiter */
1097 *t = '\0'; /* tie off host */
1098 ret = rfc822_cpy (string); /* copy host */
1099 *t = c; /* restore delimiter */
1100 *end = t; /* remember end of domain */
1101 rfc822_skipws (&t); /* skip WS after host */
1102 while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
1103 string = ++t; /* skip past the dot and any WS */
1104 rfc822_skipws (&string);
1105 if (string = rfc822_parse_domain (string,&t)) {
1106 *end = t; /* remember new end of domain */
1107 c = *t; /* remember delimiter */
1108 *t = '\0'; /* tie off host */
1109 s = rfc822_cpy (string);/* copy successor part */
1110 *t = c; /* restore delimiter */
1111 /* build new domain */
1112 sprintf (v = (char *) fs_get (strlen (ret) + strlen (s) + 2),
1113 "%s.%s",ret,s);
1114 fs_give ((void **) &ret);
1115 ret = v; /* new host name */
1116 rfc822_skipws (&t); /* skip WS after domain */
1118 else { /* barf */
1119 MM_LOG ("Invalid domain part after .",PARSE);
1120 break;
1124 else MM_LOG ("Missing or invalid host name after @",PARSE);
1125 return ret;
1128 /* Parse RFC 2822 phrase
1129 * Accepts: string pointer
1130 * Returns: pointer to end of phrase
1131 */
1133 char *rfc822_parse_phrase (char *s)
1135 char *curpos;
1136 if (!s) return NIL; /* no-op if no string */
1137 /* find first word of phrase */
1138 curpos = rfc822_parse_word (s,NIL);
1139 if (!curpos) return NIL; /* no words means no phrase */
1140 if (!*curpos) return curpos; /* check if string ends with word */
1141 s = curpos; /* sniff past the end of this word and WS */
1142 rfc822_skipws (&s); /* skip whitespace */
1143 /* recurse to see if any more */
1144 return (s = rfc822_parse_phrase (s)) ? s : curpos;
1147 /* Parse RFC 2822 word
1148 * Accepts: string pointer
1149 * delimiter (or NIL for phrase word parsing)
1150 * Returns: pointer to end of word
1151 */
1153 char *rfc822_parse_word (char *s,const char *delimiters)
1155 char *st,*str;
1156 if (!s) return NIL; /* no string */
1157 rfc822_skipws (&s); /* flush leading whitespace */
1158 if (!*s) return NIL; /* empty string */
1159 str = s; /* hunt pointer for strpbrk */
1160 while (T) { /* look for delimiter, return if none */
1161 if (!(st = strpbrk (str,delimiters ? delimiters : wspecials)))
1162 return str + strlen (str);
1163 /* ESC in phrase */
1164 if (!delimiters && (*st == I2C_ESC)) {
1165 str = ++st; /* always skip past ESC */
1166 switch (*st) { /* special hack for RFC 1468 (ISO-2022-JP) */
1167 case I2C_MULTI: /* multi byte sequence */
1168 switch (*++st) {
1169 case I2CS_94x94_JIS_OLD:/* old JIS (1978) */
1170 case I2CS_94x94_JIS_NEW:/* new JIS (1983) */
1171 str = ++st; /* skip past the shift to JIS */
1172 while (st = strchr (st,I2C_ESC))
1173 if ((*++st == I2C_G0_94) && ((st[1] == I2CS_94_ASCII) ||
1174 (st[1] == I2CS_94_JIS_ROMAN) ||
1175 (st[1] == I2CS_94_JIS_BUGROM))) {
1176 str = st += 2; /* skip past the shift back to ASCII */
1177 break;
1179 /* eats entire text if no shift back */
1180 if (!st || !*st) return str + strlen (str);
1182 break;
1183 case I2C_G0_94: /* single byte sequence */
1184 switch (st[1]) {
1185 case I2CS_94_ASCII: /* shift to ASCII */
1186 case I2CS_94_JIS_ROMAN: /* shift to JIS-Roman */
1187 case I2CS_94_JIS_BUGROM:/* old buggy definition of JIS-Roman */
1188 str = st + 2; /* skip past the shift */
1189 break;
1194 else switch (*st) { /* dispatch based on delimiter */
1195 case '"': /* quoted string */
1196 /* look for close quote */
1197 while (*++st != '"') switch (*st) {
1198 case '\0': /* unbalanced quoted string */
1199 return NIL; /* sick sick sick */
1200 case '\\': /* quoted character */
1201 if (!*++st) return NIL; /* skip the next character */
1202 default: /* ordinary character */
1203 break; /* no special action */
1205 str = ++st; /* continue parse */
1206 break;
1207 case '\\': /* quoted character */
1208 /* This is wrong; a quoted-pair can not be part of a word. However,
1209 * domain-literal is parsed as a word and quoted-pairs can be used
1210 * *there*. Either way, it's pretty pathological.
1211 */
1212 if (st[1]) { /* not on NUL though... */
1213 str = st + 2; /* skip quoted character and go on */
1214 break;
1216 default: /* found a word delimiter */
1217 return (st == s) ? NIL : st;
1222 /* Copy an RFC 2822 format string
1223 * Accepts: string
1224 * Returns: copy of string
1225 */
1227 char *rfc822_cpy (char *src)
1229 /* copy and unquote */
1230 return rfc822_quote (cpystr (src));
1234 /* Unquote an RFC 2822 format string
1235 * Accepts: string
1236 * Returns: string
1237 */
1239 char *rfc822_quote (char *src)
1241 char *ret = src;
1242 if (strpbrk (src,"\\\"")) { /* any quoting in string? */
1243 char *dst = ret;
1244 while (*src) { /* copy string */
1245 if (*src == '\"') src++; /* skip double quote entirely */
1246 else {
1247 if (*src == '\\') src++;/* skip over single quote, copy next always */
1248 *dst++ = *src++; /* copy character */
1251 *dst = '\0'; /* tie off string */
1253 return ret; /* return our string */
1257 /* Copy address list
1258 * Accepts: address list
1259 * Returns: address list
1260 */
1262 ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
1264 ADDRESS *dadr;
1265 ADDRESS *ret = NIL;
1266 ADDRESS *prev = NIL;
1267 while (adr) { /* loop while there's still an MAP adr */
1268 dadr = mail_newaddr (); /* instantiate a new address */
1269 if (!ret) ret = dadr; /* note return */
1270 if (prev) prev->next = dadr;/* tie on to the end of any previous */
1271 dadr->personal = cpystr (adr->personal);
1272 dadr->adl = cpystr (adr->adl);
1273 dadr->mailbox = cpystr (adr->mailbox);
1274 dadr->host = cpystr (adr->host);
1275 prev = dadr; /* this is now the previous */
1276 adr = adr->next; /* go to next address in list */
1278 return (ret); /* return the MTP address list */
1281 /* Skips RFC 2822 whitespace
1282 * Accepts: pointer to string pointer
1283 */
1285 void rfc822_skipws (char **s)
1287 while (T) switch (**s) {
1288 case ' ': case '\t': case '\015': case '\012':
1289 ++*s; /* skip all forms of LWSP */
1290 break;
1291 case '(': /* start of comment */
1292 if (rfc822_skip_comment (s,(long) NIL)) break;
1293 default:
1294 return; /* end of whitespace */
1299 /* Skips RFC 2822 comment
1300 * Accepts: pointer to string pointer
1301 * trim flag
1302 * Returns: pointer to first non-blank character of comment
1303 */
1305 char *rfc822_skip_comment (char **s,long trim)
1307 char *ret,tmp[MAILTMPLEN];
1308 char *s1 = *s;
1309 char *t = NIL;
1310 /* skip past whitespace */
1311 for (ret = ++s1; *ret == ' '; ret++);
1312 do switch (*s1) { /* get character of comment */
1313 case '(': /* nested comment? */
1314 if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
1315 t = --s1; /* last significant char at end of comment */
1316 break;
1317 case ')': /* end of comment? */
1318 *s = ++s1; /* skip past end of comment */
1319 if (trim) { /* if level 0, must trim */
1320 if (t) t[1] = '\0'; /* tie off comment string */
1321 else *ret = '\0'; /* empty comment */
1323 return ret;
1324 case '\\': /* quote next character? */
1325 if (*++s1) { /* next character non-null? */
1326 t = s1; /* update last significant character pointer */
1327 break; /* all OK */
1329 case '\0': /* end of string */
1330 sprintf (tmp,"Unterminated comment: %.80s",*s);
1331 MM_LOG (tmp,PARSE);
1332 **s = '\0'; /* nuke duplicate messages in case reparse */
1333 return NIL; /* this is wierd if it happens */
1334 case ' ': /* whitespace isn't significant */
1335 break;
1336 default: /* random character */
1337 t = s1; /* update last significant character pointer */
1338 break;
1339 } while (s1++);
1340 return NIL; /* impossible, but pacify lint et al */
1343 /* Buffered output routines */
1346 /* Output character to buffer
1347 * Accepts: buffer
1348 * character to write
1349 * Returns: T if success, NIL if error
1350 */
1352 static long rfc822_output_char (RFC822BUFFER *buf,int c)
1354 if ((buf->cur == buf->end) && !rfc822_output_flush (buf)) return NIL;
1355 *buf->cur++ = c; /* add character, soutr buffer if full */
1356 return (buf->cur == buf->end) ? rfc822_output_flush (buf) : LONGT;
1360 /* Output data to buffer
1361 * Accepts: buffer
1362 * data to write
1363 * size of data
1364 * Returns: T if success, NIL if error
1365 */
1367 static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len)
1369 while (len) { /* until request satified */
1370 long i;
1371 if (i = min (len,buf->end - buf->cur)) {
1372 memcpy (buf->cur,string,i);
1373 buf->cur += i; /* blat data */
1374 string += i;
1375 len -= i;
1377 /* soutr buffer now if full */
1378 if ((len || (buf->cur == buf->end)) && !rfc822_output_flush (buf))
1379 return NIL;
1381 return LONGT;
1384 /* Output string to buffer
1385 * Accepts: buffer
1386 * string to write
1387 * Returns: T if success, NIL if error
1388 */
1390 static long rfc822_output_string (RFC822BUFFER *buf,char *string)
1392 return rfc822_output_data (buf,string,strlen (string));
1396 /* Flush buffer
1397 * Accepts: buffer
1398 * I/O routine
1399 * stream for I/O routine
1400 * Returns: T if success, NIL if error
1401 */
1403 long rfc822_output_flush (RFC822BUFFER *buf)
1405 *buf->cur = '\0'; /* tie off buffer at this point */
1406 return (*buf->f) (buf->s,buf->cur = buf->beg);
1409 /* Message writing routines */
1412 /* Output RFC 822 message
1413 * Accepts: temporary buffer as a SIZEDTEXT
1414 * envelope
1415 * body
1416 * I/O routine
1417 * stream for I/O routine
1418 * non-zero if 8-bit output desired
1419 * Returns: T if successful, NIL if failure
1421 * This routine always uses standard specials for phrases and does not write
1422 * bcc entries, since it is called from the SMTP and NNTP routines. If you
1423 * need to do something different you need to arm an rfc822outfull_t and/or
1424 * rfc822out_t function.
1425 */
1427 long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8)
1429 rfc822outfull_t r822of =
1430 (rfc822outfull_t) mail_parameters (NIL,GET_RFC822OUTPUTFULL,NIL);
1431 rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
1432 /* call external RFC 2822 output generator */
1433 if (r822of) return (*r822of) (buf,env,body,ok8);
1434 else if (r822o) return (*r822o) (buf->cur,env,body,buf->f,buf->s,ok8);
1435 /* encode body as necessary */
1436 if (ok8) rfc822_encode_body_8bit (env,body);
1437 else rfc822_encode_body_7bit (env,body);
1438 /* output header and body */
1439 return rfc822_output_header (buf,env,body,NIL,NIL) &&
1440 rfc822_output_text (buf,body) && rfc822_output_flush (buf);
1443 /* Output RFC 822 header
1444 * Accepts: buffer
1445 * envelope
1446 * body
1447 * non-standard specials to be used for phrases if non-NIL
1448 * flags (non-zero to include bcc
1449 * Returns: T if success, NIL if failure
1450 */
1452 long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,
1453 const char *specials,long flags)
1455 long i = env->remail ? strlen (env->remail) : 0;
1456 return /* write header */
1457 (!i || /* snip extra CRLF from remail header */
1458 rfc822_output_data (buf,env->remail,
1459 ((i > 4) && (env->remail[i-4] == '\015')) ?
1460 i - 2 : i)) &&
1461 rfc822_output_header_line (buf,"Newsgroups",i,env->newsgroups) &&
1462 rfc822_output_header_line (buf,"Date",i,env->date) &&
1463 rfc822_output_address_line (buf,"From",i,env->from,specials) &&
1464 rfc822_output_address_line (buf,"Sender",i,env->sender,specials) &&
1465 rfc822_output_address_line (buf,"Reply-To",i,env->reply_to,specials) &&
1466 rfc822_output_header_line (buf,"Subject",i,env->subject) &&
1467 ((env->bcc && !(env->to || env->cc)) ?
1468 rfc822_output_string (buf,"To: undisclosed recipients: ;\015\012") :
1469 LONGT) &&
1470 rfc822_output_address_line (buf,"To",i,env->to,specials) &&
1471 rfc822_output_address_line (buf,"cc",i,env->cc,specials) &&
1472 (flags ? rfc822_output_address_line (buf,"bcc",i,env->bcc,specials) : T) &&
1473 rfc822_output_header_line (buf,"In-Reply-To",i,env->in_reply_to) &&
1474 rfc822_output_header_line (buf,"Message-ID",i,env->message_id) &&
1475 rfc822_output_header_line (buf,"Followup-to",i,env->followup_to) &&
1476 rfc822_output_header_line (buf,"References",i,env->references) &&
1477 (env->remail || !body ||
1478 (rfc822_output_string (buf,"MIME-Version: 1.0\015\012") &&
1479 rfc822_output_body_header (buf,body))) &&
1480 /* write terminating blank line */
1481 rfc822_output_string (buf,"\015\012");
1484 /* Output RFC 2822 header text line
1485 * Accepts: buffer
1486 * pointer to header type
1487 * non-NIL if resending
1488 * pointer to text
1489 * Returns: T if success, NIL if failure
1490 */
1492 long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent,
1493 char *text)
1495 return !text ||
1496 ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1497 rfc822_output_string (buf,type) && rfc822_output_string (buf,": ") &&
1498 rfc822_output_string (buf,text) && rfc822_output_string (buf,"\015\012"));
1502 /* Output RFC 2822 header address line
1503 * Accepts: buffer
1504 * pointer to header type
1505 * non-NIL if resending
1506 * address(s) to interpret
1507 * non-standard specials to be used for phrases if non-NIL
1508 * Returns: T if success, NIL if failure
1509 */
1511 long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent,
1512 ADDRESS *adr,const char *specials)
1514 long pretty = strlen (type);
1515 return !adr ||
1516 ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1517 rfc822_output_data (buf,type,pretty) && rfc822_output_string (buf,": ") &&
1518 rfc822_output_address_list (buf,adr,
1519 resent ? pretty + sizeof (RESENTPREFIX) - 1 :
1520 pretty,specials) &&
1521 rfc822_output_string (buf,"\015\012"));
1524 /* Output RFC 2822 address list
1525 * Accepts: buffer
1526 * pointer to address list
1527 * non-zero if pretty-printing
1528 * non-standard specials to be used for phrases if non-NIL
1529 * Returns: T if success, NIL if failure
1530 */
1532 long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty,
1533 const char *specials)
1535 long n;
1536 /* default to rspecials */
1537 if (!specials) specials = rspecials;
1538 for (n = 0; adr; adr = adr->next) {
1539 char *base = buf->cur;
1540 if (adr->host) { /* ordinary address? */
1541 if (!(pretty && n)) { /* suppress if pretty and in group */
1542 if ( /* use phrase <route-addr> if phrase */
1543 #if RFC822
1544 adr->adl || /* or A-D-L */
1545 #endif
1546 (adr->personal && *adr->personal)) {
1547 if (!((adr->personal ? rfc822_output_cat (buf,adr->personal,
1548 rspecials) : LONGT) &&
1549 rfc822_output_string (buf," <") &&
1550 rfc822_output_address (buf,adr) &&
1551 rfc822_output_string (buf,">"))) return NIL;
1553 else if (!rfc822_output_address (buf,adr)) return NIL;
1554 if (adr->next && adr->next->mailbox &&
1555 !rfc822_output_string (buf,", ")) return NIL;
1558 else if (adr->mailbox) { /* start of group? */
1559 /* yes, write group */
1560 if (!(rfc822_output_cat (buf,adr->mailbox,rspecials) &&
1561 rfc822_output_string (buf,": "))) return NIL;
1562 ++n; /* in a group now */
1564 else if (n) { /* must be end of group (but be paranoid) */
1565 if (!rfc822_output_char (buf,';') ||
1566 ((!--n && adr->next && adr->next->mailbox) &&
1567 !rfc822_output_string (buf,", "))) return NIL;
1569 if (pretty && adr->next && /* pretty printing? */
1570 ((pretty += ((buf->cur > base) ? buf->cur - base :
1571 (buf->end - base) + (buf->cur - buf->beg))) >= 78)) {
1572 if (!(rfc822_output_string (buf,"\015\012") &&
1573 rfc822_output_string (buf,RFC822CONT))) return NIL;
1574 base = buf->cur; /* update base for pretty printing */
1575 pretty = sizeof (RFC822CONT) - 1;
1578 return LONGT;
1581 /* Write RFC 2822 route-address to string
1582 * Accepts: buffer
1583 * pointer to single address
1584 * Returns: T if success, NIL if failure
1585 */
1587 long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr)
1589 return !adr || !adr->host ||
1591 #if RFC822 /* old code with A-D-L support */
1592 (!adr->adl || (rfc822_output_string (buf,adr->adl) &&
1593 rfc822_output_char (buf,':'))) &&
1594 #endif
1595 rfc822_output_cat (buf,adr->mailbox,NIL) &&
1596 ((*adr->host == '@') || /* unless null host (HIGHLY discouraged!) */
1597 (rfc822_output_char (buf,'@') &&
1598 rfc822_output_cat (buf,adr->host,NIL))));
1602 /* Output RFC 2822 string with concatenation
1603 * Accepts: buffer
1604 * string to concatenate
1605 * list of special characters or NIL for dot-atom format
1606 * Returns: T if success, NIL if failure
1607 */
1609 long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials)
1611 char *s;
1612 if (!*src || /* empty string or any specials present? */
1613 (specials ? (T && strpbrk (src,specials)) :
1614 (strpbrk (src,wspecials) || (*src == '.') || strstr (src,"..") ||
1615 (src[strlen (src) - 1] == '.')))) {
1616 /* yes, write as quoted string*/
1617 if (!rfc822_output_char (buf,'"')) return NIL;
1618 /* embedded quote characters? */
1619 for (; s = strpbrk (src,"\\\""); src = s + 1) {
1620 /* yes, insert quoting */
1621 if (!(rfc822_output_data (buf,src,s-src) &&
1622 rfc822_output_char (buf,'\\') &&
1623 rfc822_output_char (buf,*s))) return NIL;
1625 /* return string and trailing quote*/
1626 return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"');
1628 /* easy case */
1629 return rfc822_output_string (buf,src);
1632 /* Output MIME parameter list
1633 * Accepts: buffer
1634 * parameter list
1635 * Returns: T if success, NIL if failure
1636 */
1638 long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param)
1640 while (param) {
1641 if (rfc822_output_string (buf,"; ") &&
1642 rfc822_output_string (buf,param->attribute) &&
1643 rfc822_output_char (buf,'=') &&
1644 rfc822_output_cat (buf,param->value,tspecials)) param = param->next;
1645 else return NIL;
1647 return LONGT;
1651 /* Output RFC 2822 stringlist
1652 * Accepts: buffer
1653 * stringlist
1654 * Returns: T if success, NIL if failure
1655 */
1657 long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl)
1659 while (stl)
1660 if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) ||
1661 ((stl = stl->next) && !rfc822_output_string (buf,", ")))
1662 return NIL;
1663 return LONGT;
1666 /* Output body content header
1667 * Accepts: buffer
1668 * body to interpret
1669 * Returns: T if success, NIL if failure
1670 */
1672 long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body)
1674 return /* type and subtype*/
1675 rfc822_output_string (buf,"Content-Type: ") &&
1676 rfc822_output_string (buf,body_types[body->type]) &&
1677 rfc822_output_char (buf,'/') &&
1678 rfc822_output_string (buf,body->subtype ? body->subtype :
1679 rfc822_default_subtype (body->type)) &&
1680 /* parameters (w/ US-ASCII default */
1681 (body->parameter ? rfc822_output_parameter (buf,body->parameter) :
1682 ((body->type != TYPETEXT) ||
1683 (rfc822_output_string (buf,"; CHARSET=") &&
1684 rfc822_output_string (buf,(body->encoding == ENC7BIT) ?
1685 "US-ASCII" : "X-UNKNOWN")))) &&
1686 (!body->encoding || /* note: 7BIT never output as encoding! */
1687 (rfc822_output_string (buf,"\015\012Content-Transfer-Encoding: ") &&
1688 rfc822_output_string (buf,body_encodings[body->encoding]))) &&
1689 (!body->id || /* identification */
1690 (rfc822_output_string (buf,"\015\012Content-ID: ") &&
1691 rfc822_output_string (buf,body->id))) &&
1692 (!body->description || /* description */
1693 (rfc822_output_string (buf,"\015\012Content-Description: ") &&
1694 rfc822_output_string (buf,body->description))) &&
1695 (!body->md5 || /* MD5 checksum */
1696 (rfc822_output_string (buf,"\015\012Content-MD5: ") &&
1697 rfc822_output_string (buf,body->md5))) &&
1698 (!body->language || /* language */
1699 (rfc822_output_string (buf,"\015\012Content-Language: ") &&
1700 rfc822_output_stringlist (buf,body->language))) &&
1701 (!body->location || /* location */
1702 (rfc822_output_string (buf,"\015\012Content-Location: ") &&
1703 rfc822_output_string (buf,body->location))) &&
1704 (!body->disposition.type || /* disposition */
1705 (rfc822_output_string (buf,"\015\012Content-Disposition: ") &&
1706 rfc822_output_string (buf,body->disposition.type) &&
1707 rfc822_output_parameter (buf,body->disposition.parameter))) &&
1708 rfc822_output_string (buf,"\015\012");
1711 /* Encode a body for 7BIT transmittal
1712 * Accepts: envelope
1713 * body
1714 */
1716 void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
1718 void *f;
1719 PART *part;
1720 PARAMETER **param;
1721 if (body) switch (body->type) {
1722 case TYPEMULTIPART: /* multi-part */
1723 for (param = &body->parameter;
1724 *param && strcmp ((*param)->attribute,"BOUNDARY");
1725 param = &(*param)->next);
1726 if (!*param) { /* cookie not set up yet? */
1727 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
1728 sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1729 (unsigned long) random (),(unsigned long) time (0),
1730 (unsigned long) getpid ());
1731 (*param) = mail_newbody_parameter ();
1732 (*param)->attribute = cpystr ("BOUNDARY");
1733 (*param)->value = cpystr (tmp);
1735 part = body->nested.part; /* encode body parts */
1736 do rfc822_encode_body_7bit (env,&part->body);
1737 while (part = part->next); /* until done */
1738 break;
1739 case TYPEMESSAGE: /* encapsulated message */
1740 switch (body->encoding) {
1741 case ENC7BIT:
1742 break;
1743 case ENC8BIT:
1744 MM_LOG ("8-bit included message in 7-bit message body",PARSE);
1745 break;
1746 case ENCBINARY:
1747 MM_LOG ("Binary included message in 7-bit message body",PARSE);
1748 break;
1749 default:
1750 fatal ("Invalid rfc822_encode_body_7bit message encoding");
1752 break; /* can't change encoding */
1753 default: /* all else has some encoding */
1754 switch (body->encoding) {
1755 case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
1756 /* remember old 8-bit contents */
1757 f = (void *) body->contents.text.data;
1758 body->contents.text.data =
1759 rfc822_8bit (body->contents.text.data,
1760 body->contents.text.size,&body->contents.text.size);
1761 body->encoding = ENCQUOTEDPRINTABLE;
1762 fs_give (&f); /* flush old binary contents */
1763 break;
1764 case ENCBINARY: /* encode binary into BASE64 */
1765 /* remember old binary contents */
1766 f = (void *) body->contents.text.data;
1767 body->contents.text.data =
1768 rfc822_binary ((void *) body->contents.text.data,
1769 body->contents.text.size,&body->contents.text.size);
1770 body->encoding = ENCBASE64;
1771 fs_give (&f); /* flush old binary contents */
1772 default: /* otherwise OK */
1773 break;
1775 break;
1779 /* Encode a body for 8BIT transmittal
1780 * Accepts: envelope
1781 * body
1782 */
1784 void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
1786 void *f;
1787 PART *part;
1788 PARAMETER **param;
1789 if (body) switch (body->type) {
1790 case TYPEMULTIPART: /* multi-part */
1791 for (param = &body->parameter;
1792 *param && strcmp ((*param)->attribute,"BOUNDARY");
1793 param = &(*param)->next);
1794 if (!*param) { /* cookie not set up yet? */
1795 char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
1796 sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1797 (unsigned long) random (),(unsigned long) time (0),
1798 (unsigned long) getpid ());
1799 (*param) = mail_newbody_parameter ();
1800 (*param)->attribute = cpystr ("BOUNDARY");
1801 (*param)->value = cpystr (tmp);
1803 part = body->nested.part; /* encode body parts */
1804 do rfc822_encode_body_8bit (env,&part->body);
1805 while (part = part->next); /* until done */
1806 break;
1807 case TYPEMESSAGE: /* encapsulated message */
1808 switch (body->encoding) {
1809 case ENC7BIT:
1810 case ENC8BIT:
1811 break;
1812 case ENCBINARY:
1813 MM_LOG ("Binary included message in 8-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: /* other type, encode binary into BASE64 */
1820 if (body->encoding == ENCBINARY) {
1821 /* remember old binary contents */
1822 f = (void *) body->contents.text.data;
1823 body->contents.text.data =
1824 rfc822_binary ((void *) body->contents.text.data,
1825 body->contents.text.size,&body->contents.text.size);
1826 body->encoding = ENCBASE64;
1827 fs_give (&f); /* flush old binary contents */
1829 break;
1833 /* Output RFC 822 text
1834 * Accepts: buffer
1835 * body
1836 * Returns: T if successful, NIL if failure
1837 */
1839 long rfc822_output_text (RFC822BUFFER *buf,BODY *body)
1841 /* MULTIPART gets special handling */
1842 if (body->type == TYPEMULTIPART) {
1843 char *cookie,tmp[MAILTMPLEN];
1844 PARAMETER *param;
1845 PART *part;
1846 /* find cookie */
1847 for (param = body->parameter; param && strcmp (param->attribute,"BOUNDARY");
1848 param = param->next);
1849 if (param) cookie = param->value;
1850 else { /* make cookie not in BASE64 or QUOTEPRINT*/
1851 sprintf (cookie = tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1852 (unsigned long) random (),(unsigned long) time (0),
1853 (unsigned long) getpid ());
1854 (param = mail_newbody_parameter ())->attribute = cpystr ("BOUNDARY");
1855 param->value = cpystr (tmp);
1856 param->next = body->parameter;
1857 body->parameter = param;
1859 /* output each part */
1860 for (part = body->nested.part; part; part = part->next)
1861 if (!(rfc822_output_string (buf,"--") &&
1862 rfc822_output_string (buf,cookie) &&
1863 rfc822_output_string (buf,"\015\012") &&
1864 rfc822_output_body_header (buf,&part->body) &&
1865 rfc822_output_string (buf,"\015\012") &&
1866 rfc822_output_text (buf,&part->body))) return NIL;
1867 /* output trailing cookie */
1868 return rfc822_output_string (buf,"--") &&
1869 rfc822_output_string (buf,cookie) &&
1870 rfc822_output_string (buf,"--\015\012");
1872 /* output segment and trailing CRLF */
1873 return (!body->contents.text.data ||
1874 rfc822_output_string (buf,(char *) body->contents.text.data)) &&
1875 rfc822_output_string (buf,"\015\012");
1878 /* Body contents encoding/decoding routines */
1881 /* Convert BASE64 contents to binary
1882 * Accepts: source
1883 * length of source
1884 * pointer to return destination length
1885 * Returns: destination as binary or NIL if error
1886 */
1888 #define WSP 0176 /* NUL, TAB, LF, FF, CR, SPC */
1889 #define JNK 0177
1890 #define PAD 0100
1892 void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
1894 char c,*s,tmp[MAILTMPLEN];
1895 void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1);
1896 char *d = (char *) ret;
1897 int e;
1898 static char decode[256] = {
1899 WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,WSP,WSP,JNK,WSP,WSP,JNK,JNK,
1900 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1901 WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,076,JNK,JNK,JNK,077,
1902 064,065,066,067,070,071,072,073,074,075,JNK,JNK,JNK,PAD,JNK,JNK,
1903 JNK,000,001,002,003,004,005,006,007,010,011,012,013,014,015,016,
1904 017,020,021,022,023,024,025,026,027,030,031,JNK,JNK,JNK,JNK,JNK,
1905 JNK,032,033,034,035,036,037,040,041,042,043,044,045,046,047,050,
1906 051,052,053,054,055,056,057,060,061,062,063,JNK,JNK,JNK,JNK,JNK,
1907 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1908 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1909 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1910 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1911 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1912 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1913 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1914 JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK
1915 };
1916 /* initialize block */
1917 memset (ret,0,((size_t) *len) + 1);
1918 *len = 0; /* in case we return an error */
1920 /* simple-minded decode */
1921 for (e = 0; srcl--; ) switch (c = decode[*src++]) {
1922 default: /* valid BASE64 data character */
1923 switch (e++) { /* install based on quantum position */
1924 case 0:
1925 *d = c << 2; /* byte 1: high 6 bits */
1926 break;
1927 case 1:
1928 *d++ |= c >> 4; /* byte 1: low 2 bits */
1929 *d = c << 4; /* byte 2: high 4 bits */
1930 break;
1931 case 2:
1932 *d++ |= c >> 2; /* byte 2: low 4 bits */
1933 *d = c << 6; /* byte 3: high 2 bits */
1934 break;
1935 case 3:
1936 *d++ |= c; /* byte 3: low 6 bits */
1937 e = 0; /* reinitialize mechanism */
1938 break;
1940 break;
1941 case WSP: /* whitespace */
1942 break;
1943 case PAD: /* padding */
1944 switch (e++) { /* check quantum position */
1945 case 3: /* one = is good enough in quantum 3 */
1946 /* make sure no data characters in remainder */
1947 for (; srcl; --srcl) switch (decode[*src++]) {
1948 /* ignore space, junk and extraneous padding */
1949 case WSP: case JNK: case PAD:
1950 break;
1951 default: /* valid BASE64 data character */
1952 /* This indicates bad MIME. One way that it can be caused is if
1953 a single-section message was BASE64 encoded and then something
1954 (e.g. a mailing list processor) appended text. The problem is
1955 that in 1 out of 3 cases, there is no padding and hence no way
1956 to detect the end of the data. Consequently, prudent software
1957 will always encapsulate a BASE64 segment inside a MULTIPART.
1958 */
1959 sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s",
1960 (char *) src - 1);
1961 if (s = strpbrk (tmp,"\015\012")) *s = NIL;
1962 mm_log (tmp,PARSE);
1963 srcl = 1; /* don't issue any more messages */
1964 break;
1966 break;
1967 case 2: /* expect a second = in quantum 2 */
1968 if (srcl && (*src == '=')) break;
1969 default: /* impossible quantum position */
1970 fs_give (&ret);
1971 return NIL;
1973 break;
1974 case JNK: /* junk character */
1975 fs_give (&ret);
1976 return NIL;
1978 *len = d - (char *) ret; /* calculate data length */
1979 *d = '\0'; /* NUL terminate just in case */
1980 return ret; /* return the string */
1983 /* Convert binary contents to BASE64
1984 * Accepts: source
1985 * length of source
1986 * pointer to return destination length
1987 * Returns: destination as BASE64
1988 */
1990 unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
1992 unsigned char *ret,*d;
1993 unsigned char *s = (unsigned char *) src;
1994 char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1995 unsigned long i = ((srcl + 2) / 3) * 4;
1996 *len = i += 2 * ((i / 60) + 1);
1997 d = ret = (unsigned char *) fs_get ((size_t) ++i);
1998 /* process tuplets */
1999 for (i = 0; srcl >= 3; s += 3, srcl -= 3) {
2000 *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
2001 /* byte 2: low 2 bits (1), high 4 bits (2) */
2002 *d++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f];
2003 /* byte 3: low 4 bits (2), high 2 bits (3) */
2004 *d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
2005 *d++ = v[s[2] & 0x3f]; /* byte 4: low 6 bits (3) */
2006 if ((++i) == 15) { /* output 60 characters? */
2007 i = 0; /* restart line break count, insert CRLF */
2008 *d++ = '\015'; *d++ = '\012';
2011 if (srcl) {
2012 *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
2013 /* byte 2: low 2 bits (1), high 4 bits (2) */
2014 *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
2015 /* byte 3: low 4 bits (2), high 2 bits (3) */
2016 *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
2017 /* byte 4: low 6 bits (3) */
2018 *d++ = srcl ? v[s[2] & 0x3f] : '=';
2019 if (srcl) srcl--; /* count third character if processed */
2020 if ((++i) == 15) { /* output 60 characters? */
2021 i = 0; /* restart line break count, insert CRLF */
2022 *d++ = '\015'; *d++ = '\012';
2025 *d++ = '\015'; *d++ = '\012'; /* insert final CRLF */
2026 *d = '\0'; /* tie off string */
2027 if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
2028 return ret; /* return the resulting string */
2031 /* Convert QUOTED-PRINTABLE contents to 8BIT
2032 * Accepts: source
2033 * length of source
2034 * pointer to return destination length
2035 * Returns: destination as 8-bit text or NIL if error
2036 */
2038 unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
2039 unsigned long *len)
2041 char tmp[MAILTMPLEN];
2042 unsigned int bogon = NIL;
2043 unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
2044 unsigned char *d = ret;
2045 unsigned char *t = d;
2046 unsigned char *s = src;
2047 unsigned char c,e;
2048 *len = 0; /* in case we return an error */
2049 /* until run out of characters */
2050 while (((unsigned long) (s - src)) < srcl) {
2051 switch (c = *s++) { /* what type of character is it? */
2052 case '=': /* quoting character */
2053 if (((unsigned long) (s - src)) < srcl) switch (c = *s++) {
2054 case '\0': /* end of data */
2055 s--; /* back up pointer */
2056 break;
2057 case '\015': /* non-significant line break */
2058 if ((((unsigned long) (s - src)) < srcl) && (*s == '\012')) s++;
2059 case '\012': /* bare LF */
2060 t = d; /* accept any leading spaces */
2061 break;
2062 default: /* two hex digits then */
2063 if (!(isxdigit (c) && (((unsigned long) (s - src)) < srcl) &&
2064 (e = *s++) && isxdigit (e))) {
2065 /* This indicates bad MIME. One way that it can be caused is if
2066 a single-section message was QUOTED-PRINTABLE encoded and then
2067 something (e.g. a mailing list processor) appended text. The
2068 problem is that there is no way to determine where the encoded
2069 data ended and the appended crud began. Consequently, prudent
2070 software will always encapsulate a QUOTED-PRINTABLE segment
2071 inside a MULTIPART.
2072 */
2073 if (!bogon++) { /* only do this once */
2074 sprintf (tmp,"Invalid quoted-printable sequence: =%.80s",
2075 (char *) s - 1);
2076 mm_log (tmp,PARSE);
2078 *d++ = '='; /* treat = as ordinary character */
2079 *d++ = c; /* and the character following */
2080 t = d; /* note point of non-space */
2081 break;
2083 *d++ = hex2byte (c,e); /* merge the two hex digits */
2084 t = d; /* note point of non-space */
2085 break;
2087 break;
2088 case ' ': /* space, possibly bogus */
2089 *d++ = c; /* stash the space but don't update s */
2090 break;
2091 case '\015': /* end of line */
2092 case '\012': /* bare LF */
2093 d = t; /* slide back to last non-space, drop in */
2094 default:
2095 *d++ = c; /* stash the character */
2096 t = d; /* note point of non-space */
2099 *d = '\0'; /* tie off results */
2100 *len = d - ret; /* calculate length */
2101 return ret; /* return the string */
2104 /* Convert 8BIT contents to QUOTED-PRINTABLE
2105 * Accepts: source
2106 * length of source
2107 * pointer to return destination length
2108 * Returns: destination as quoted-printable text
2109 */
2111 #define MAXL (size_t) 75 /* 76th position only used by continuation = */
2113 unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
2114 unsigned long *len)
2116 unsigned long lp = 0;
2117 unsigned char *ret = (unsigned char *)
2118 fs_get ((size_t) (3*srcl + 3*(((3*srcl)/MAXL) + 1)));
2119 unsigned char *d = ret;
2120 char *hex = "0123456789ABCDEF";
2121 unsigned char c;
2122 while (srcl--) { /* for each character */
2123 /* true line break? */
2124 if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
2125 *d++ = '\015'; *d++ = *src++; srcl--;
2126 lp = 0; /* reset line count */
2128 else { /* not a line break */
2129 /* quoting required? */
2130 if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
2131 ((c == ' ') && (*src == '\015'))) {
2132 if ((lp += 3) > MAXL) { /* yes, would line overflow? */
2133 *d++ = '='; *d++ = '\015'; *d++ = '\012';
2134 lp = 3; /* set line count */
2136 *d++ = '='; /* quote character */
2137 *d++ = hex[c >> 4]; /* high order 4 bits */
2138 *d++ = hex[c & 0xf]; /* low order 4 bits */
2140 else { /* ordinary character */
2141 if ((++lp) > MAXL) { /* would line overflow? */
2142 *d++ = '='; *d++ = '\015'; *d++ = '\012';
2143 lp = 1; /* set line count */
2145 *d++ = c; /* ordinary character */
2149 *d = '\0'; /* tie off destination */
2150 *len = d - ret; /* calculate true size */
2151 /* try to give some space back */
2152 fs_resize ((void **) &ret,(size_t) *len + 1);
2153 return ret;
2156 /* Legacy Routines */
2158 /*
2159 * WARNING: These routines are for compatibility with old software only.
2161 * Their use in new software is to be avoided.
2163 * These interfaces do not provide satisfactory buffer checking. In
2164 * versions of c-client prior to imap-2005, they did not provide any
2165 * buffer checking at all.
2167 * As a half-hearted attempt, these new compatability functions for the
2168 * legacy interfaces limit what they write to size SENDBUFLEN and will
2169 * fatal() if more than that is written. However, that isn't good enough
2170 * since several of these functions *append* to the buffer, and return an
2171 * updated pointer. Consequently, there is no way of knowing what the
2172 * actual available space is in the buffer, yet the function will still
2173 * write up to SENDBUFLEN bytes even if there is much less space actually
2174 * available. The result is a buffer overflow.
2176 * You won't get a buffer overflow if you never attempt to append using
2177 * these interfaces, but you can get the fatal() if it tries to write
2178 * more than SENDBUFLEN bytes.
2180 * To avoid this problem, use the corresponding rfc822_output_???()
2181 * functions instead, e.g., rfc822_output_address() instead of
2182 * rfc822_address().
2184 */
2186 /* Flush routine, only called if overflow
2187 * Accepts: stream
2188 * string to output
2189 * Returns: never
2190 */
2192 static long rfc822_legacy_soutr (void *stream,char *string)
2194 fatal ("rfc822.c legacy routine buffer overflow");
2195 return NIL;
2198 /* Legacy write RFC 2822 header from message structure
2199 * Accepts: scratch buffer to write into
2200 * message envelope
2201 * message body
2202 */
2204 void rfc822_header (char *header,ENVELOPE *env,BODY *body)
2206 RFC822BUFFER buf;
2207 /* write at start of buffer */
2208 buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1;
2209 buf.f = rfc822_legacy_soutr;
2210 buf.s = NIL;
2211 rfc822_output_header (&buf,env,body,NIL,NIL);
2212 *buf.cur = '\0'; /* tie off buffer */
2216 /* Legacy write RFC 2822 text from header line
2217 * Accepts: pointer to destination string pointer
2218 * pointer to header type
2219 * message to interpret
2220 * pointer to text
2221 */
2223 void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
2225 RFC822BUFFER buf;
2226 /* append to buffer */
2227 buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2228 buf.f = rfc822_legacy_soutr;
2229 buf.s = NIL;
2230 rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text);
2231 *(*header = buf.cur) = '\0'; /* tie off buffer */
2234 /* Legacy write RFC 2822 address from header line
2235 * Accepts: pointer to destination string pointer
2236 * pointer to header type
2237 * message to interpret
2238 * address to interpret
2239 */
2241 void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
2243 RFC822BUFFER buf;
2244 /* append to buffer */
2245 buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2246 buf.f = rfc822_legacy_soutr;
2247 buf.s = NIL;
2248 rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL);
2249 *(*header = buf.cur) = '\0'; /* tie off buffer */
2253 /* Legacy write RFC 2822 address list
2254 * Accepts: pointer to destination string
2255 * address to interpret
2256 * header base if pretty-printing
2257 * Returns: end of destination string
2258 */
2260 char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
2262 RFC822BUFFER buf;
2263 /* append to buffer */
2264 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2265 buf.f = rfc822_legacy_soutr;
2266 buf.s = NIL;
2267 rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL);
2268 *buf.cur = '\0'; /* tie off buffer */
2269 return buf.cur;
2273 /* Legacy write RFC 2822 route-address to string
2274 * Accepts: pointer to destination string
2275 * address to interpret
2276 */
2278 void rfc822_address (char *dest,ADDRESS *adr)
2280 RFC822BUFFER buf;
2281 /* append to buffer */
2282 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2283 buf.f = rfc822_legacy_soutr;
2284 buf.s = NIL;
2285 rfc822_output_address (&buf,adr);
2286 *buf.cur = '\0'; /* tie off buffer */
2289 /* Concatenate RFC 2822 string
2290 * Accepts: pointer to destination string
2291 * pointer to string to concatenate
2292 * list of special characters or NIL for dot-atom format
2293 */
2295 void rfc822_cat (char *dest,char *src,const char *specials)
2297 RFC822BUFFER buf;
2298 /* append to buffer */
2299 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2300 buf.f = rfc822_legacy_soutr;
2301 buf.s = NIL;
2302 rfc822_output_cat (&buf,src,specials);
2303 *buf.cur = '\0'; /* tie off buffer */
2307 /* Legacy write body content header
2308 * Accepts: pointer to destination string pointer
2309 * pointer to body to interpret
2310 */
2312 void rfc822_write_body_header (char **dst,BODY *body)
2314 RFC822BUFFER buf;
2315 /* append to buffer */
2316 buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1;
2317 buf.f = rfc822_legacy_soutr;
2318 buf.s = NIL;
2319 rfc822_output_body_header (&buf,body);
2320 *(*dst = buf.cur) = '\0'; /* tie off buffer */
2323 /* Legacy output RFC 822 message
2324 * Accepts: temporary buffer
2325 * envelope
2326 * body
2327 * I/O routine
2328 * stream for I/O routine
2329 * non-zero if 8-bit output desired
2330 * Returns: T if successful, NIL if failure
2331 */
2333 long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s,
2334 long ok8bit)
2336 long ret;
2337 rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
2338 /* call external RFC 2822 output generator */
2339 if (r822o) ret = (*r822o) (t,env,body,f,s,ok8bit);
2340 else { /* output generator not armed */
2341 RFC822BUFFER buf; /* use our own buffer rather than trust */
2342 char tmp[SENDBUFLEN+1]; /* client to give us a big enough one */
2343 buf.f = f;
2344 buf.s = s;
2345 buf.end = (buf.beg = buf.cur = t) + SENDBUFLEN - 1;
2346 tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
2347 ret = rfc822_output_full (&buf,env,body,ok8bit);
2349 return ret;
2353 /* Legacy output RFC 822 body
2354 * Accepts: body
2355 * I/O routine
2356 * stream for I/O routine
2357 * Returns: T if successful, NIL if failure
2358 */
2360 long rfc822_output_body (BODY *body,soutr_t f,void *s)
2362 RFC822BUFFER buf;
2363 char tmp[SENDBUFLEN+1];
2364 buf.f = f;
2365 buf.s = s;
2366 buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
2367 tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
2368 return rfc822_output_text (&buf,body) && rfc822_output_flush (&buf);

UW-IMAP'd extensions by yuuji