]> diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
change mhlist to use decimal math when abbreviating sizes
[nmh] / uip / mhbuildsbr.c
1
2 /*
3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
8 */
9
10 /*
11 * This code was originally part of mhn.c. I split it into
12 * a separate program (mhbuild.c) and then later split it
13 * again (mhbuildsbr.c). But the code still has some of
14 * the mhn.c code in it. This program needs additional
15 * streamlining and removal of unneeded code.
16 */
17
18 #include <h/mh.h>
19 #include <fcntl.h>
20 #include <h/signals.h>
21 #include <h/md5.h>
22 #include <h/mts.h>
23 #include <h/tws.h>
24 #include <h/mime.h>
25 #include <h/mhparse.h>
26 #include <h/utils.h>
27
28 #ifdef HAVE_SYS_TIME_H
29 # include <sys/time.h>
30 #endif
31 #include <time.h>
32
33
34 extern int debugsw;
35
36 extern int listsw;
37 extern int rfc934sw;
38 extern int contentidsw;
39
40 /* cache policies */
41 extern int rcachesw; /* mhcachesbr.c */
42 extern int wcachesw; /* mhcachesbr.c */
43
44 static char prefix[] = "----- =_aaaaaaaaaa";
45
46 struct attach_list {
47 char *filename;
48 struct attach_list *next;
49 };
50
51 /*
52 * Maximum size of URL token in message/external-body
53 */
54
55 #define MAXURLTOKEN 40
56
57
58 /* mhmisc.c */
59 void content_error (char *, CT, char *, ...);
60
61 /* mhcachesbr.c */
62 int find_cache (CT, int, int *, char *, char *, int);
63
64 /* mhfree.c */
65 void free_ctinfo (CT);
66 void free_encoding (CT, int);
67
68 /*
69 * static prototypes
70 */
71 static int init_decoded_content (CT, const char *);
72 static void setup_attach_content(CT, char *);
73 static char *fgetstr (char *, int, FILE *);
74 static int user_content (FILE *, char *, CT *, const char *infilename);
75 static void set_id (CT, int);
76 static int compose_content (CT, int);
77 static int scan_content (CT, size_t);
78 static int build_headers (CT, int);
79 static char *calculate_digest (CT, int);
80
81
82 static unsigned char directives_stack[32];
83 static unsigned int directives_index;
84
85 static int do_direct(void)
86 {
87 return directives_stack[directives_index];
88 }
89
90 static void directive_onoff(int onoff)
91 {
92 if (directives_index >= sizeof(directives_stack) - 1) {
93 fprintf(stderr, "mhbuild: #on/off overflow, continuing\n");
94 return;
95 }
96 directives_stack[++directives_index] = onoff;
97 }
98
99 static void directive_init(int onoff)
100 {
101 directives_index = 0;
102 directives_stack[0] = onoff;
103 }
104
105 static void directive_pop(void)
106 {
107 if (directives_index > 0)
108 directives_index--;
109 else
110 fprintf(stderr, "mhbuild: #pop underflow, continuing\n");
111 }
112
113 /*
114 * Main routine for translating composition file
115 * into valid MIME message. It translates the draft
116 * into a content structure (actually a tree of content
117 * structures). This message then can be manipulated
118 * in various ways, including being output via
119 * output_message().
120 */
121
122 CT
123 build_mime (char *infile, int autobuild, int dist, int directives,
124 int header_encoding, size_t maxunencoded, int verbose)
125 {
126 int compnum, state;
127 char buf[BUFSIZ], name[NAMESZ];
128 char *cp, *np, *vp;
129 struct multipart *m;
130 struct part **pp;
131 CT ct;
132 FILE *in;
133 HF hp;
134 m_getfld_state_t gstate = 0;
135 struct attach_list *attach_head = NULL, *attach_tail = NULL, *at_entry;
136
137 directive_init(directives);
138
139 umask (~m_gmprot ());
140
141 /* open the composition draft */
142 if ((in = fopen (infile, "r")) == NULL)
143 adios (infile, "unable to open for reading");
144
145 /*
146 * Allocate space for primary (outside) content
147 */
148 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
149 adios (NULL, "out of memory");
150
151 /*
152 * Allocate structure for handling decoded content
153 * for this part. We don't really need this, but
154 * allocate it to remain consistent.
155 */
156 init_decoded_content (ct, infile);
157
158 /*
159 * Parse some of the header fields in the composition
160 * draft into the linked list of header fields for
161 * the new MIME message.
162 */
163 m_getfld_track_filepos (&gstate, in);
164 for (compnum = 1;;) {
165 int bufsz = sizeof buf;
166 switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
167 case FLD:
168 case FLDPLUS:
169 compnum++;
170
171 /* abort if draft has Mime-Version or C-T-E header field */
172 if (strcasecmp (name, VRSN_FIELD) == 0 ||
173 strcasecmp (name, ENCODING_FIELD) == 0) {
174 if (autobuild) {
175 fclose(in);
176 return NULL;
177 } else {
178 adios (NULL, "draft shouldn't contain %s: field", name);
179 }
180 }
181
182 /* ignore any Content-Type fields in the header */
183 if (!strcasecmp (name, TYPE_FIELD)) {
184 while (state == FLDPLUS) {
185 bufsz = sizeof buf;
186 state = m_getfld (&gstate, name, buf, &bufsz, in);
187 }
188 goto finish_field;
189 }
190
191 /* get copies of the buffers */
192 np = add (name, NULL);
193 vp = add (buf, NULL);
194
195 /* if necessary, get rest of field */
196 while (state == FLDPLUS) {
197 bufsz = sizeof buf;
198 state = m_getfld (&gstate, name, buf, &bufsz, in);
199 vp = add (buf, vp); /* add to previous value */
200 }
201
202 /*
203 * Now add the header data to the list, unless it's an attach
204 * header; in that case, add it to our attach list
205 */
206
207 if (strcasecmp(ATTACH_FIELD, np) == 0) {
208 struct attach_list *entry;
209 char *s = vp, *e = vp + strlen(vp) - 1;
210 free(np);
211
212 /*
213 * Make sure we can find the start of this filename.
214 * If it's blank, we skip completely. Otherwise, strip
215 * off any leading spaces and trailing newlines.
216 */
217
218 while (isspace((unsigned char) *s))
219 s++;
220
221 while (e > s && *e == '\n')
222 *e-- = '\0';
223
224 if (*s == '\0') {
225 free(vp);
226 goto finish_field;
227 }
228
229 entry = mh_xmalloc(sizeof(*entry));
230 entry->filename = getcpy(s);
231 entry->next = NULL;
232 free(vp);
233
234 if (attach_tail) {
235 attach_tail->next = entry;
236 attach_tail = entry;
237 } else {
238 attach_head = attach_tail = entry;
239 }
240 } else {
241 add_header (ct, np, vp);
242 }
243
244 finish_field:
245 /* if this wasn't the last header field, then continue */
246 continue;
247
248 case BODY:
249 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
250 /* fall through */
251 case FILEEOF:
252 break;
253
254 case LENERR:
255 case FMTERR:
256 adios (NULL, "message format error in component #%d", compnum);
257
258 default:
259 adios (NULL, "getfld() returned %d", state);
260 }
261 break;
262 }
263 m_getfld_state_destroy (&gstate);
264
265 /*
266 * Iterate through the list of headers and call the function to MIME-ify
267 * them if required.
268 */
269
270 for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
271 if (encode_rfc2047(hp->name, &hp->value, header_encoding, NULL)) {
272 adios(NULL, "Unable to encode header \"%s\"", hp->name);
273 }
274 }
275
276 /*
277 * Now add the MIME-Version header field
278 * to the list of header fields.
279 */
280
281 if (! dist) {
282 np = add (VRSN_FIELD, NULL);
283 vp = concat (" ", VRSN_VALUE, "\n", NULL);
284 add_header (ct, np, vp);
285 }
286
287 /*
288 * We initally assume we will find multiple contents in the
289 * draft. So create a multipart/mixed content to hold everything.
290 * We can remove this later, if it is not needed.
291 */
292 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
293 done (1);
294 ct->c_type = CT_MULTIPART;
295 ct->c_subtype = MULTI_MIXED;
296
297 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
298 adios (NULL, "out of memory");
299 ct->c_ctparams = (void *) m;
300 pp = &m->mp_parts;
301
302 /*
303 * read and parse the composition file
304 * and the directives it contains.
305 */
306 while (fgetstr (buf, sizeof(buf) - 1, in)) {
307 struct part *part;
308 CT p;
309
310 if (user_content (in, buf, &p, infile) == DONE) {
311 admonish (NULL, "ignoring spurious #end");
312 continue;
313 }
314 if (!p)
315 continue;
316
317 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
318 adios (NULL, "out of memory");
319 *pp = part;
320 pp = &part->mp_next;
321 part->mp_part = p;
322 }
323
324 /*
325 * Add any Attach headers to the list of MIME parts at the end of the
326 * message.
327 */
328
329 for (at_entry = attach_head; at_entry; ) {
330 struct attach_list *at_prev = at_entry;
331 struct part *part;
332 CT p;
333
334 if (access(at_entry->filename, R_OK) != 0) {
335 adios("reading", "Unable to open %s for", at_entry->filename);
336 }
337
338 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
339 adios(NULL, "out of memory");
340
341 init_decoded_content(p, infile);
342
343 /*
344 * Initialize our content structure based on the filename,
345 * and fill in all of the relevant fields. Also place MIME
346 * parameters in the attributes array.
347 */
348
349 setup_attach_content(p, at_entry->filename);
350
351 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
352 adios (NULL, "out of memory");
353 *pp = part;
354 pp = &part->mp_next;
355 part->mp_part = p;
356
357 at_entry = at_entry->next;
358 free(at_prev->filename);
359 free(at_prev);
360 }
361
362 /*
363 * To allow for empty message bodies, if we've found NO content at all
364 * yet cook up an empty text/plain part.
365 */
366
367 if (!m->mp_parts) {
368 CT p;
369 struct part *part;
370 struct text *t;
371
372 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
373 adios(NULL, "out of memory");
374
375 init_decoded_content(p, infile);
376
377 if (get_ctinfo ("text/plain", p, 0) == NOTOK)
378 done (1);
379
380 p->c_type = CT_TEXT;
381 p->c_subtype = TEXT_PLAIN;
382 p->c_encoding = CE_7BIT;
383 /*
384 * Sigh. ce_file contains the "decoded" contents of this part.
385 * So this seems like the best option available since we're going
386 * to call scan_content() on this.
387 */
388 p->c_cefile.ce_file = getcpy("/dev/null");
389 p->c_begin = ftell(in);
390 p->c_end = ftell(in);
391
392 if ((t = (struct text *) calloc (1, sizeof (*t))) == NULL)
393 adios (NULL, "out of memory");
394
395 t->tx_charset = CHARSET_SPECIFIED;
396 p->c_ctparams = t;
397
398 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
399 adios (NULL, "out of memory");
400 *pp = part;
401 pp = &part->mp_next;
402 part->mp_part = p;
403 }
404
405 /*
406 * close the composition draft since
407 * it's not needed any longer.
408 */
409 fclose (in);
410
411 /*
412 * If only one content was found, then remove and
413 * free the outer multipart content.
414 */
415 if (!m->mp_parts->mp_next) {
416 CT p;
417
418 p = m->mp_parts->mp_part;
419 m->mp_parts->mp_part = NULL;
420
421 /* move header fields */
422 p->c_first_hf = ct->c_first_hf;
423 p->c_last_hf = ct->c_last_hf;
424 ct->c_first_hf = NULL;
425 ct->c_last_hf = NULL;
426
427 free_content (ct);
428 ct = p;
429 } else {
430 set_id (ct, 1);
431 }
432
433 /*
434 * Fill out, or expand directives. Parse and execute
435 * commands specified by profile composition strings.
436 */
437 compose_content (ct, verbose);
438
439 if ((cp = strchr(prefix, 'a')) == NULL)
440 adios (NULL, "internal error(4)");
441
442 /*
443 * Scan the contents. Choose a transfer encoding, and
444 * check if prefix for multipart boundary clashes with
445 * any of the contents.
446 */
447 while (scan_content (ct, maxunencoded) == NOTOK) {
448 if (*cp < 'z') {
449 (*cp)++;
450 } else {
451 if (*++cp == 0)
452 adios (NULL, "giving up trying to find a unique delimiter string");
453 else
454 (*cp)++;
455 }
456 }
457
458 /* Build the rest of the header field structures */
459 if (! dist)
460 build_headers (ct, header_encoding);
461
462 return ct;
463 }
464
465
466 /*
467 * Set up structures for placing unencoded
468 * content when building parts.
469 */
470
471 static int
472 init_decoded_content (CT ct, const char *filename)
473 {
474 ct->c_ceopenfnx = open7Bit; /* since unencoded */
475 ct->c_ceclosefnx = close_encoding;
476 ct->c_cesizefnx = NULL; /* since unencoded */
477 ct->c_encoding = CE_7BIT; /* Seems like a reasonable default */
478 ct->c_file = add(filename, NULL);
479
480 return OK;
481 }
482
483
484 static char *
485 fgetstr (char *s, int n, FILE *stream)
486 {
487 char *cp, *ep;
488 int o_n = n;
489
490 while(1) {
491 for (ep = (cp = s) + o_n; cp < ep; ) {
492 int i;
493
494 if (!fgets (cp, n, stream))
495 return (cp != s ? s : NULL);
496
497 if (cp == s && *cp != '#')
498 return s;
499
500 cp += (i = strlen (cp)) - 1;
501 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
502 break;
503 *cp = '\0';
504 n -= (i - 2);
505 }
506
507 if (strcmp(s, "#on\n") == 0) {
508 directive_onoff(1);
509 } else if (strcmp(s, "#off\n") == 0) {
510 directive_onoff(0);
511 } else if (strcmp(s, "#pop\n") == 0) {
512 directive_pop();
513 } else {
514 break;
515 }
516 }
517
518 return s;
519 }
520
521
522 /*
523 * Parse the composition draft for text and directives.
524 * Do initial setup of Content structure.
525 */
526
527 static int
528 user_content (FILE *in, char *buf, CT *ctp, const char *infilename)
529 {
530 int extrnal, vrsn;
531 char *cp, **ap;
532 char buffer[BUFSIZ];
533 struct multipart *m;
534 struct part **pp;
535 struct stat st;
536 struct str2init *s2i;
537 CI ci;
538 CT ct;
539 CE ce;
540
541 if (buf[0] == '\n' || (do_direct() && strcmp (buf, "#\n") == 0)) {
542 *ctp = NULL;
543 return OK;
544 }
545
546 /* allocate basic Content structure */
547 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
548 adios (NULL, "out of memory");
549 *ctp = ct;
550
551 /* allocate basic structure for handling decoded content */
552 init_decoded_content (ct, infilename);
553 ce = &ct->c_cefile;
554
555 ci = &ct->c_ctinfo;
556 set_id (ct, 0);
557
558 /*
559 * Handle inline text. Check if line
560 * is one of the following forms:
561 *
562 * 1) doesn't begin with '#' (implicit directive)
563 * 2) begins with "##" (implicit directive)
564 * 3) begins with "#<"
565 */
566 if (!do_direct() || buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
567 int headers;
568 int inlineD;
569 long pos;
570 char content[BUFSIZ];
571 FILE *out;
572 char *cp;
573
574 if ((cp = m_mktemp2(NULL, invo_name, NULL, &out)) == NULL) {
575 adios("mhbuildsbr", "unable to create temporary file in %s",
576 get_temp_dir());
577 }
578
579 /* use a temp file to collect the plain text lines */
580 ce->ce_file = add (cp, NULL);
581 ce->ce_unlink = 1;
582
583 if (do_direct() && (buf[0] == '#' && buf[1] == '<')) {
584 strncpy (content, buf + 2, sizeof(content));
585 inlineD = 1;
586 goto rock_and_roll;
587 } else {
588 inlineD = 0;
589 }
590
591 /* the directive is implicit */
592 strncpy (content, "text/plain", sizeof(content));
593 headers = 0;
594 strncpy (buffer, (!do_direct() || buf[0] != '#') ? buf : buf + 1, sizeof(buffer));
595 for (;;) {
596 int i;
597
598 if (headers >= 0 && do_direct() && uprf (buffer, DESCR_FIELD)
599 && buffer[i = strlen (DESCR_FIELD)] == ':') {
600 headers = 1;
601
602 again_descr:
603 ct->c_descr = add (buffer + i + 1, ct->c_descr);
604 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
605 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
606 switch (buffer[0]) {
607 case ' ':
608 case '\t':
609 i = -1;
610 goto again_descr;
611
612 case '#':
613 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
614 /* NOTREACHED */
615
616 default:
617 break;
618 }
619 }
620
621 if (headers >= 0 && do_direct() && uprf (buffer, DISPO_FIELD)
622 && buffer[i = strlen (DISPO_FIELD)] == ':') {
623 headers = 1;
624
625 again_dispo:
626 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
627 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
628 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
629 switch (buffer[0]) {
630 case ' ':
631 case '\t':
632 i = -1;
633 goto again_dispo;
634
635 case '#':
636 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
637 /* NOTREACHED */
638
639 default:
640 break;
641 }
642 }
643
644 if (headers != 1 || buffer[0] != '\n')
645 fputs (buffer, out);
646
647 rock_and_roll:
648 headers = -1;
649 pos = ftell (in);
650 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
651 break;
652 if (do_direct() && buffer[0] == '#') {
653 char *bp;
654
655 if (buffer[1] != '#')
656 break;
657 for (cp = (bp = buffer) + 1; *cp; cp++)
658 *bp++ = *cp;
659 *bp = '\0';
660 }
661 }
662
663 if (listsw)
664 ct->c_end = ftell (out);
665 fclose (out);
666
667 /* parse content type */
668 if (get_ctinfo (content, ct, inlineD) == NOTOK)
669 done (1);
670
671 for (s2i = str2cts; s2i->si_key; s2i++)
672 if (!strcasecmp (ci->ci_type, s2i->si_key))
673 break;
674 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
675 s2i++;
676
677 /*
678 * check type specified (possibly implicitly)
679 */
680 switch (ct->c_type = s2i->si_val) {
681 case CT_MESSAGE:
682 if (!strcasecmp (ci->ci_subtype, "rfc822")) {
683 ct->c_encoding = CE_7BIT;
684 goto call_init;
685 }
686 /* else fall... */
687 case CT_MULTIPART:
688 adios (NULL, "it doesn't make sense to define an in-line %s content",
689 ct->c_type == CT_MESSAGE ? "message" : "multipart");
690 /* NOTREACHED */
691
692 default:
693 call_init:
694 if ((ct->c_ctinitfnx = s2i->si_init))
695 (*ct->c_ctinitfnx) (ct);
696 break;
697 }
698
699 if (cp)
700 fseek (in, pos, SEEK_SET);
701 return OK;
702 }
703
704 /*
705 * If we've reached this point, the next line
706 * must be some type of explicit directive.
707 */
708
709 /* check if directive is external-type */
710 extrnal = (buf[1] == '@');
711
712 /* parse directive */
713 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
714 done (1);
715
716 /* check directive against the list of MIME types */
717 for (s2i = str2cts; s2i->si_key; s2i++)
718 if (!strcasecmp (ci->ci_type, s2i->si_key))
719 break;
720
721 /*
722 * Check if the directive specified a valid type.
723 * This will happen if it was one of the following forms:
724 *
725 * #type/subtype (or)
726 * #@type/subtype
727 */
728 if (s2i->si_key) {
729 if (!ci->ci_subtype)
730 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
731
732 switch (ct->c_type = s2i->si_val) {
733 case CT_MULTIPART:
734 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
735 ci->ci_type, ci->ci_subtype);
736 /* NOTREACHED */
737
738 case CT_MESSAGE:
739 if (!strcasecmp (ci->ci_subtype, "partial"))
740 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
741 ci->ci_type, ci->ci_subtype);
742 if (!strcasecmp (ci->ci_subtype, "external-body"))
743 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
744 ci->ci_type, ci->ci_subtype);
745 use_forw:
746 adios (NULL,
747 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
748 ci->ci_type, ci->ci_subtype);
749 /* NOTREACHED */
750
751 default:
752 if ((ct->c_ctinitfnx = s2i->si_init))
753 (*ct->c_ctinitfnx) (ct);
754 break;
755 }
756
757 /*
758 * #@type/subtype (external types directive)
759 */
760 if (extrnal) {
761 struct exbody *e;
762 CT p;
763
764 if (!ci->ci_magic)
765 adios (NULL, "need external information for \"#@%s/%s\"",
766 ci->ci_type, ci->ci_subtype);
767 p = ct;
768
769 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
770 free (ci->ci_magic);
771 ci->ci_magic = NULL;
772
773 /*
774 * Since we are using the current Content structure to
775 * hold information about the type of the external
776 * reference, we need to create another Content structure
777 * for the message/external-body to wrap it in.
778 */
779 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
780 adios (NULL, "out of memory");
781 init_decoded_content(ct, infilename);
782 *ctp = ct;
783 ci = &ct->c_ctinfo;
784 if (get_ctinfo (buffer, ct, 0) == NOTOK)
785 done (1);
786 ct->c_type = CT_MESSAGE;
787 ct->c_subtype = MESSAGE_EXTERNAL;
788
789 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
790 adios (NULL, "out of memory");
791 ct->c_ctparams = (void *) e;
792
793 e->eb_parent = ct;
794 e->eb_content = p;
795 p->c_ctexbody = e;
796
797 if (params_external (ct, 1) == NOTOK)
798 done (1);
799
800 return OK;
801 }
802
803 /* Handle [file] argument */
804 if (ci->ci_magic) {
805 /* check if specifies command to execute */
806 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
807 for (cp = ci->ci_magic + 1; isspace ((unsigned char) *cp); cp++)
808 continue;
809 if (!*cp)
810 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
811 cp = add (cp, NULL);
812 free (ci->ci_magic);
813 ci->ci_magic = cp;
814 } else {
815 /* record filename of decoded contents */
816 ce->ce_file = ci->ci_magic;
817 if (access (ce->ce_file, R_OK) == NOTOK)
818 adios ("reading", "unable to access %s for", ce->ce_file);
819 if (listsw && stat (ce->ce_file, &st) != NOTOK)
820 ct->c_end = (long) st.st_size;
821 ci->ci_magic = NULL;
822 }
823 return OK;
824 }
825
826 /*
827 * No [file] argument, so check profile for
828 * method to compose content.
829 */
830 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
831 invo_name, ci->ci_type, ci->ci_subtype);
832 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
833 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
834 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
835 content_error (NULL, ct, "don't know how to compose content");
836 done (1);
837 }
838 }
839 ci->ci_magic = add (cp, NULL);
840 return OK;
841 }
842
843 if (extrnal)
844 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
845
846 /*
847 * Message directive
848 * #forw [+folder] [msgs]
849 */
850 if (!strcasecmp (ci->ci_type, "forw")) {
851 int msgnum;
852 char *folder, *arguments[MAXARGS];
853 struct msgs *mp;
854
855 if (ci->ci_magic) {
856 ap = brkstring (ci->ci_magic, " ", "\n");
857 copyip (ap, arguments, MAXARGS);
858 } else {
859 arguments[0] = "cur";
860 arguments[1] = NULL;
861 }
862 folder = NULL;
863
864 /* search the arguments for a folder name */
865 for (ap = arguments; *ap; ap++) {
866 cp = *ap;
867 if (*cp == '+' || *cp == '@') {
868 if (folder)
869 adios (NULL, "only one folder per #forw directive");
870 else
871 folder = pluspath (cp);
872 }
873 }
874
875 /* else, use the current folder */
876 if (!folder)
877 folder = add (getfolder (1), NULL);
878
879 if (!(mp = folder_read (folder, 0)))
880 adios (NULL, "unable to read folder %s", folder);
881 for (ap = arguments; *ap; ap++) {
882 cp = *ap;
883 if (*cp != '+' && *cp != '@')
884 if (!m_convert (mp, cp))
885 done (1);
886 }
887 free (folder);
888 free_ctinfo (ct);
889
890 /*
891 * If there is more than one message to include, make this
892 * a content of type "multipart/digest" and insert each message
893 * as a subpart. If there is only one message, then make this
894 * a content of type "message/rfc822".
895 */
896 if (mp->numsel > 1) {
897 /* we are forwarding multiple messages */
898 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
899 done (1);
900 ct->c_type = CT_MULTIPART;
901 ct->c_subtype = MULTI_DIGEST;
902
903 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
904 adios (NULL, "out of memory");
905 ct->c_ctparams = (void *) m;
906 pp = &m->mp_parts;
907
908 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
909 if (is_selected(mp, msgnum)) {
910 struct part *part;
911 CT p;
912 CE pe;
913
914 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
915 adios (NULL, "out of memory");
916 init_decoded_content (p, infilename);
917 pe = &p->c_cefile;
918 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
919 done (1);
920 p->c_type = CT_MESSAGE;
921 p->c_subtype = MESSAGE_RFC822;
922
923 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
924 pe->ce_file = add (buffer, NULL);
925 if (listsw && stat (pe->ce_file, &st) != NOTOK)
926 p->c_end = (long) st.st_size;
927
928 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
929 adios (NULL, "out of memory");
930 *pp = part;
931 pp = &part->mp_next;
932 part->mp_part = p;
933 }
934 }
935 } else {
936 /* we are forwarding one message */
937 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
938 done (1);
939 ct->c_type = CT_MESSAGE;
940 ct->c_subtype = MESSAGE_RFC822;
941
942 msgnum = mp->lowsel;
943 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
944 ce->ce_file = add (buffer, NULL);
945 if (listsw && stat (ce->ce_file, &st) != NOTOK)
946 ct->c_end = (long) st.st_size;
947 }
948
949 folder_free (mp); /* free folder/message structure */
950 return OK;
951 }
952
953 /*
954 * #end
955 */
956 if (!strcasecmp (ci->ci_type, "end")) {
957 free_content (ct);
958 *ctp = NULL;
959 return DONE;
960 }
961
962 /*
963 * #begin [ alternative | parallel ]
964 */
965 if (!strcasecmp (ci->ci_type, "begin")) {
966 if (!ci->ci_magic) {
967 vrsn = MULTI_MIXED;
968 cp = SubMultiPart[vrsn - 1].kv_key;
969 } else if (!strcasecmp (ci->ci_magic, "alternative")) {
970 vrsn = MULTI_ALTERNATE;
971 cp = SubMultiPart[vrsn - 1].kv_key;
972 } else if (!strcasecmp (ci->ci_magic, "parallel")) {
973 vrsn = MULTI_PARALLEL;
974 cp = SubMultiPart[vrsn - 1].kv_key;
975 } else if (uprf (ci->ci_magic, "digest")) {
976 goto use_forw;
977 } else {
978 vrsn = MULTI_UNKNOWN;
979 cp = ci->ci_magic;
980 }
981
982 free_ctinfo (ct);
983 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
984 if (get_ctinfo (buffer, ct, 0) == NOTOK)
985 done (1);
986 ct->c_type = CT_MULTIPART;
987 ct->c_subtype = vrsn;
988
989 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
990 adios (NULL, "out of memory");
991 ct->c_ctparams = (void *) m;
992
993 pp = &m->mp_parts;
994 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
995 struct part *part;
996 CT p;
997
998 if (user_content (in, buffer, &p, infilename) == DONE) {
999 if (!m->mp_parts)
1000 adios (NULL, "empty \"#begin ... #end\" sequence");
1001 return OK;
1002 }
1003 if (!p)
1004 continue;
1005
1006 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1007 adios (NULL, "out of memory");
1008 *pp = part;
1009 pp = &part->mp_next;
1010 part->mp_part = p;
1011 }
1012 admonish (NULL, "premature end-of-file, missing #end");
1013 return OK;
1014 }
1015
1016 /*
1017 * Unknown directive
1018 */
1019 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
1020 return NOTOK; /* NOT REACHED */
1021 }
1022
1023
1024 static void
1025 set_id (CT ct, int top)
1026 {
1027 char contentid[BUFSIZ];
1028 static int partno;
1029 static time_t clock = 0;
1030 static char *msgfmt;
1031
1032 if (clock == 0) {
1033 time (&clock);
1034 snprintf (contentid, sizeof(contentid), "%s\n", message_id (clock, 1));
1035 partno = 0;
1036 msgfmt = getcpy(contentid);
1037 }
1038 snprintf (contentid, sizeof(contentid), msgfmt, top ? 0 : ++partno);
1039 ct->c_id = getcpy (contentid);
1040 }
1041
1042
1043 /*
1044 * Fill out, or expand the various contents in the composition
1045 * draft. Read-in any necessary files. Parse and execute any
1046 * commands specified by profile composition strings.
1047 */
1048
1049 static int
1050 compose_content (CT ct, int verbose)
1051 {
1052 CE ce = &ct->c_cefile;
1053
1054 switch (ct->c_type) {
1055 case CT_MULTIPART:
1056 {
1057 int partnum;
1058 char *pp;
1059 char partnam[BUFSIZ];
1060 struct multipart *m = (struct multipart *) ct->c_ctparams;
1061 struct part *part;
1062
1063 if (ct->c_partno) {
1064 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1065 pp = partnam + strlen (partnam);
1066 } else {
1067 pp = partnam;
1068 }
1069
1070 /* first, we call compose_content on all the subparts */
1071 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
1072 CT p = part->mp_part;
1073
1074 sprintf (pp, "%d", partnum);
1075 p->c_partno = add (partnam, NULL);
1076 if (compose_content (p, verbose) == NOTOK)
1077 return NOTOK;
1078 }
1079
1080 /*
1081 * If the -rfc934mode switch is given, then check all
1082 * the subparts of a multipart/digest. If they are all
1083 * message/rfc822, then mark this content and all
1084 * subparts with the rfc934 compatibility mode flag.
1085 */
1086 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1087 int is934 = 1;
1088
1089 for (part = m->mp_parts; part; part = part->mp_next) {
1090 CT p = part->mp_part;
1091
1092 if (p->c_subtype != MESSAGE_RFC822) {
1093 is934 = 0;
1094 break;
1095 }
1096 }
1097 ct->c_rfc934 = is934;
1098 for (part = m->mp_parts; part; part = part->mp_next) {
1099 CT p = part->mp_part;
1100
1101 if ((p->c_rfc934 = is934))
1102 p->c_end++;
1103 }
1104 }
1105
1106 if (listsw) {
1107 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
1108 if (ct->c_rfc934)
1109 ct->c_end += 1;
1110
1111 for (part = m->mp_parts; part; part = part->mp_next)
1112 ct->c_end += part->mp_part->c_end + partnum;
1113 }
1114 }
1115 break;
1116
1117 case CT_MESSAGE:
1118 /* Nothing to do for type message */
1119 break;
1120
1121 /*
1122 * Discrete types (text/application/audio/image/video)
1123 */
1124 default:
1125 if (!ce->ce_file) {
1126 pid_t child_id;
1127 int i, xstdout, len, buflen;
1128 char *bp, *cp;
1129 char *vec[4], buffer[BUFSIZ];
1130 FILE *out;
1131 CI ci = &ct->c_ctinfo;
1132 char *tfile = NULL;
1133
1134 if (!(cp = ci->ci_magic))
1135 adios (NULL, "internal error(5)");
1136
1137 if ((tfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1138 adios("mhbuildsbr", "unable to create temporary file in %s",
1139 get_temp_dir());
1140 }
1141 ce->ce_file = add (tfile, NULL);
1142 ce->ce_unlink = 1;
1143
1144 xstdout = 0;
1145
1146 /* Get buffer ready to go */
1147 bp = buffer;
1148 bp[0] = '\0';
1149 buflen = sizeof(buffer);
1150
1151 /*
1152 * Parse composition string into buffer
1153 */
1154 for ( ; *cp; cp++) {
1155 if (*cp == '%') {
1156 switch (*++cp) {
1157 case 'a':
1158 {
1159 /* insert parameters from directive */
1160 char *s = "";
1161 PM pm;
1162
1163 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1164 snprintf (bp, buflen, "%s%s=\"%s\"", s,
1165 pm->pm_name, get_param_value(pm, '?'));
1166 len = strlen (bp);
1167 bp += len;
1168 buflen -= len;
1169 s = " ";
1170 }
1171 }
1172 break;
1173
1174 case 'F':
1175 /* %f, and stdout is not-redirected */
1176 xstdout = 1;
1177 /* and fall... */
1178
1179 case 'f':
1180 /*
1181 * insert temporary filename where
1182 * content should be written
1183 */
1184 snprintf (bp, buflen, "%s", ce->ce_file);
1185 break;
1186
1187 case 's':
1188 /* insert content subtype */
1189 strncpy (bp, ci->ci_subtype, buflen);
1190 break;
1191
1192 case '%':
1193 /* insert character % */
1194 goto raw;
1195
1196 default:
1197 *bp++ = *--cp;
1198 *bp = '\0';
1199 buflen--;
1200 continue;
1201 }
1202 len = strlen (bp);
1203 bp += len;
1204 buflen -= len;
1205 } else {
1206 raw:
1207 *bp++ = *cp;
1208 *bp = '\0';
1209 buflen--;
1210 }
1211 }
1212
1213 if (verbose)
1214 printf ("composing content %s/%s from command\n\t%s\n",
1215 ci->ci_type, ci->ci_subtype, buffer);
1216
1217 fflush (stdout); /* not sure if need for -noverbose */
1218
1219 vec[0] = "/bin/sh";
1220 vec[1] = "-c";
1221 vec[2] = buffer;
1222 vec[3] = NULL;
1223
1224 if ((out = fopen (ce->ce_file, "w")) == NULL)
1225 adios (ce->ce_file, "unable to open for writing");
1226
1227 for (i = 0; (child_id = fork()) == NOTOK && i > 5; i++)
1228 sleep (5);
1229 switch (child_id) {
1230 case NOTOK:
1231 adios ("fork", "unable to fork");
1232 /* NOTREACHED */
1233
1234 case OK:
1235 if (!xstdout)
1236 dup2 (fileno (out), 1);
1237 close (fileno (out));
1238 execvp ("/bin/sh", vec);
1239 fprintf (stderr, "unable to exec ");
1240 perror ("/bin/sh");
1241 _exit (-1);
1242 /* NOTREACHED */
1243
1244 default:
1245 fclose (out);
1246 if (pidXwait(child_id, NULL))
1247 done (1);
1248 break;
1249 }
1250 }
1251
1252 /* Check size of file */
1253 if (listsw && ct->c_end == 0L) {
1254 struct stat st;
1255
1256 if (stat (ce->ce_file, &st) != NOTOK)
1257 ct->c_end = (long) st.st_size;
1258 }
1259 break;
1260 }
1261
1262 return OK;
1263 }
1264
1265
1266 /*
1267 * Scan the content.
1268 *
1269 * 1) choose a transfer encoding.
1270 * 2) check for clashes with multipart boundary string.
1271 * 3) for text content, figure out which character set is being used.
1272 *
1273 * If there is a clash with one of the contents and the multipart boundary,
1274 * this function will exit with NOTOK. This will cause the scanning process
1275 * to be repeated with a different multipart boundary. It is possible
1276 * (although highly unlikely) that this scan will be repeated multiple times.
1277 */
1278
1279 static int
1280 scan_content (CT ct, size_t maxunencoded)
1281 {
1282 int prefix_len;
1283 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1284 int checknul = 0, containsnul = 0; /* check if contains NULs */
1285 int checklinelen = 0, linelen = 0; /* check for long lines */
1286 int checkllinelen = 0; /* check for extra-long lines */
1287 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1288 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1289 char *cp = NULL;
1290 char *bufp = NULL;
1291 size_t buflen;
1292 ssize_t gotlen;
1293 struct text *t = NULL;
1294 FILE *in = NULL;
1295 CE ce = &ct->c_cefile;
1296
1297 /*
1298 * handle multipart by scanning all subparts
1299 * and then checking their encoding.
1300 */
1301 if (ct->c_type == CT_MULTIPART) {
1302 struct multipart *m = (struct multipart *) ct->c_ctparams;
1303 struct part *part;
1304
1305 /* initially mark the domain of enclosing multipart as 7bit */
1306 ct->c_encoding = CE_7BIT;
1307
1308 for (part = m->mp_parts; part; part = part->mp_next) {
1309 CT p = part->mp_part;
1310
1311 if (scan_content (p, maxunencoded) == NOTOK) /* choose encoding for subpart */
1312 return NOTOK;
1313
1314 /* if necessary, enlarge encoding for enclosing multipart */
1315 if (p->c_encoding == CE_BINARY)
1316 ct->c_encoding = CE_BINARY;
1317 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1318 ct->c_encoding = CE_8BIT;
1319 }
1320
1321 return OK;
1322 }
1323
1324 /*
1325 * Decide what to check while scanning this content. Note that
1326 * for text content we always check for 8bit characters if the
1327 * charset is unspecified, because that controls whether or not the
1328 * character set is us-ascii or retrieved from the locale.
1329 */
1330
1331 if (ct->c_type == CT_TEXT) {
1332 t = (struct text *) ct->c_ctparams;
1333 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1334 check8bit = 1;
1335 checknul = 1;
1336 }
1337 }
1338
1339 switch (ct->c_reqencoding) {
1340 case CE_8BIT:
1341 checkllinelen = 1;
1342 checkboundary = 1;
1343 break;
1344 case CE_QUOTED:
1345 checkboundary = 1;
1346 break;
1347 case CE_BASE64:
1348 break;
1349 case CE_UNKNOWN:
1350 /* Use the default rules based on content-type */
1351 switch (ct->c_type) {
1352 case CT_TEXT:
1353 checkboundary = 1;
1354 checklinelen = 1;
1355 if (ct->c_subtype == TEXT_PLAIN) {
1356 checklinespace = 0;
1357 } else {
1358 checklinespace = 1;
1359 }
1360 break;
1361
1362 case CT_APPLICATION:
1363 check8bit = 1;
1364 checknul = 1;
1365 checklinelen = 1;
1366 checklinespace = 1;
1367 checkboundary = 1;
1368 break;
1369
1370 case CT_MESSAGE:
1371 checklinelen = 0;
1372 checklinespace = 0;
1373
1374 /* don't check anything for message/external */
1375 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1376 checkboundary = 0;
1377 check8bit = 0;
1378 } else {
1379 checkboundary = 1;
1380 check8bit = 1;
1381 }
1382 break;
1383
1384 case CT_AUDIO:
1385 case CT_IMAGE:
1386 case CT_VIDEO:
1387 /*
1388 * Don't check anything for these types,
1389 * since we are forcing use of base64, unless
1390 * the content-type was specified by a mhbuild directive.
1391 */
1392 check8bit = 0;
1393 checklinelen = 0;
1394 checklinespace = 0;
1395 checkboundary = 0;
1396 break;
1397 }
1398 }
1399
1400 /*
1401 * Scan the unencoded content
1402 */
1403 if (check8bit || checklinelen || checklinespace || checkboundary ||
1404 checkllinelen || checknul) {
1405 if ((in = fopen (ce->ce_file, "r")) == NULL)
1406 adios (ce->ce_file, "unable to open for reading");
1407 prefix_len = strlen (prefix);
1408
1409 while ((gotlen = getline(&bufp, &buflen, in)) != -1) {
1410 /*
1411 * Check for 8bit and NUL data.
1412 */
1413 for (cp = bufp; (check8bit || checknul) &&
1414 cp < bufp + gotlen; cp++) {
1415 if (!isascii ((unsigned char) *cp)) {
1416 contains8bit = 1;
1417 check8bit = 0; /* no need to keep checking */
1418 }
1419 if (!*cp) {
1420 containsnul = 1;
1421 checknul = 0; /* no need to keep checking */
1422 }
1423 }
1424
1425 /*
1426 * Check line length.
1427 */
1428 if (checklinelen && ((size_t)gotlen > maxunencoded + 1)) {
1429 linelen = 1;
1430 checklinelen = 0; /* no need to keep checking */
1431 }
1432
1433 /*
1434 * RFC 5322 specifies that a message cannot contain a line
1435 * greater than 998 characters (excluding the CRLF). If we
1436 * get one of those lines and linelen is NOT set, then abort.
1437 */
1438
1439 if (checkllinelen && !linelen &&
1440 (gotlen > MAXLONGLINE + 1)) {
1441 adios(NULL, "Line in content exceeds maximum line limit (%d)",
1442 MAXLONGLINE);
1443 }
1444
1445 /*
1446 * Check if line ends with a space.
1447 */
1448 if (checklinespace && (cp = bufp + gotlen - 2) > bufp &&
1449 isspace ((unsigned char) *cp)) {
1450 linespace = 1;
1451 checklinespace = 0; /* no need to keep checking */
1452 }
1453
1454 /*
1455 * Check if content contains a line that clashes
1456 * with our standard boundary for multipart messages.
1457 */
1458 if (checkboundary && bufp[0] == '-' && bufp[1] == '-') {
1459 for (cp = bufp + gotlen - 1; cp >= bufp; cp--)
1460 if (!isspace ((unsigned char) *cp))
1461 break;
1462 *++cp = '\0';
1463 if (!strncmp(bufp + 2, prefix, prefix_len) &&
1464 isdigit((unsigned char) bufp[2 + prefix_len])) {
1465 boundaryclash = 1;
1466 checkboundary = 0; /* no need to keep checking */
1467 }
1468 }
1469 }
1470 fclose (in);
1471 free(bufp);
1472 }
1473
1474 /*
1475 * If the content is text and didn't specify a character set,
1476 * we need to figure out which one was used.
1477 */
1478
1479 if (ct->c_type == CT_TEXT) {
1480 t = (struct text *) ct->c_ctparams;
1481 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1482 CI ci = &ct->c_ctinfo;
1483
1484 add_param(&ci->ci_first_pm, &ci->ci_last_pm, "charset",
1485 contains8bit ? write_charset_8bit() : "us-ascii", 0);
1486 t->tx_charset = CHARSET_SPECIFIED;
1487 }
1488 }
1489
1490 /*
1491 * Decide which transfer encoding to use.
1492 */
1493
1494 if (ct->c_reqencoding != CE_UNKNOWN)
1495 ct->c_encoding = ct->c_reqencoding;
1496 else
1497 switch (ct->c_type) {
1498 case CT_TEXT:
1499 if (contains8bit && !containsnul && !linelen && !linespace && !checksw)
1500 ct->c_encoding = CE_8BIT;
1501 else if (contains8bit || containsnul || linelen || linespace || checksw)
1502 ct->c_encoding = CE_QUOTED;
1503 else
1504 ct->c_encoding = CE_7BIT;
1505 break;
1506
1507 case CT_APPLICATION:
1508 /* For application type, use base64, except when postscript */
1509 if (containsnul || contains8bit || linelen || linespace || checksw)
1510 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1511 ? CE_QUOTED : CE_BASE64;
1512 else
1513 ct->c_encoding = CE_7BIT;
1514 break;
1515
1516 case CT_MESSAGE:
1517 ct->c_encoding = contains8bit ? CE_8BIT : CE_7BIT;
1518 break;
1519
1520 case CT_AUDIO:
1521 case CT_IMAGE:
1522 case CT_VIDEO:
1523 /* For audio, image, and video contents, just use base64 */
1524 ct->c_encoding = CE_BASE64;
1525 break;
1526 }
1527
1528 return (boundaryclash ? NOTOK : OK);
1529 }
1530
1531
1532 /*
1533 * Scan the content structures, and build header
1534 * fields that will need to be output into the
1535 * message.
1536 */
1537
1538 static int
1539 build_headers (CT ct, int header_encoding)
1540 {
1541 int cc, mailbody, extbody, len;
1542 char *np, *vp, buffer[BUFSIZ];
1543 CI ci = &ct->c_ctinfo;
1544
1545 /*
1546 * If message is type multipart, then add the multipart
1547 * boundary to the list of attribute/value pairs.
1548 */
1549 if (ct->c_type == CT_MULTIPART) {
1550 static int level = 0; /* store nesting level */
1551
1552 snprintf (buffer, sizeof(buffer), "%s%d", prefix, level++);
1553 add_param(&ci->ci_first_pm, &ci->ci_last_pm, "boundary", buffer, 0);
1554 }
1555
1556 /*
1557 * Skip the output of Content-Type, parameters, content
1558 * description and disposition, and Content-ID if the
1559 * content is of type "message" and the rfc934 compatibility
1560 * flag is set (which means we are inside multipart/digest
1561 * and the switch -rfc934mode was given).
1562 */
1563 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1564 goto skip_headers;
1565
1566 /*
1567 * output the content type and subtype
1568 */
1569 np = add (TYPE_FIELD, NULL);
1570 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1571
1572 /* keep track of length of line */
1573 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1574 + strlen (ci->ci_subtype) + 3;
1575
1576 extbody = ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_EXTERNAL;
1577 mailbody = extbody && ((struct exbody *) ct->c_ctparams)->eb_body;
1578
1579 /*
1580 * Append the attribute/value pairs to
1581 * the end of the Content-Type line.
1582 */
1583
1584 if (ci->ci_first_pm) {
1585 char *s = output_params(len, ci->ci_first_pm, &len, mailbody);
1586
1587 if (!s)
1588 adios(NULL, "Internal error: failed outputting Content-Type "
1589 "parameters");
1590
1591 vp = add (s, vp);
1592 free(s);
1593 }
1594
1595 /*
1596 * Append any RFC-822 comment to the end of
1597 * the Content-Type line.
1598 */
1599 if (ci->ci_comment) {
1600 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1601 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1602 vp = add ("\n\t", vp);
1603 len = 8;
1604 } else {
1605 vp = add (" ", vp);
1606 len++;
1607 }
1608 vp = add (buffer, vp);
1609 len += cc;
1610 }
1611 vp = add ("\n", vp);
1612 add_header (ct, np, vp);
1613
1614 /*
1615 * output the Content-ID, unless disabled by -nocontentid
1616 */
1617 if (contentidsw && ct->c_id) {
1618 np = add (ID_FIELD, NULL);
1619 vp = concat (" ", ct->c_id, NULL);
1620 add_header (ct, np, vp);
1621 }
1622 /*
1623 * output the Content-Description
1624 */
1625 if (ct->c_descr) {
1626 np = add (DESCR_FIELD, NULL);
1627 vp = concat (" ", ct->c_descr, NULL);
1628 if (encode_rfc2047(DESCR_FIELD, &vp, header_encoding, NULL))
1629 adios(NULL, "Unable to encode %s header", DESCR_FIELD);
1630 add_header (ct, np, vp);
1631 }
1632
1633 /*
1634 * output the Content-Disposition. If it's NULL but c_dispo_type is
1635 * set, then we need to build it.
1636 */
1637 if (ct->c_dispo) {
1638 np = add (DISPO_FIELD, NULL);
1639 vp = concat (" ", ct->c_dispo, NULL);
1640 add_header (ct, np, vp);
1641 } else if (ct->c_dispo_type) {
1642 vp = concat (" ", ct->c_dispo_type, NULL);
1643 len = strlen(DISPO_FIELD) + strlen(vp) + 1;
1644 np = output_params(len, ct->c_dispo_first, NULL, 0);
1645 vp = add(np, vp);
1646 vp = add("\n", vp);
1647 if (np)
1648 free(np);
1649 add_header (ct, getcpy(DISPO_FIELD), vp);
1650 }
1651
1652 skip_headers:
1653 /*
1654 * If this is the internal content structure for a
1655 * "message/external", then we are done with the
1656 * headers (since it has no body).
1657 */
1658 if (ct->c_ctexbody)
1659 return OK;
1660
1661 /*
1662 * output the Content-MD5
1663 */
1664 if (checksw) {
1665 np = add (MD5_FIELD, NULL);
1666 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1667 add_header (ct, np, vp);
1668 }
1669
1670 /*
1671 * output the Content-Transfer-Encoding
1672 */
1673 switch (ct->c_encoding) {
1674 case CE_7BIT:
1675 /* Nothing to output */
1676 break;
1677
1678 case CE_8BIT:
1679 np = add (ENCODING_FIELD, NULL);
1680 vp = concat (" ", "8bit", "\n", NULL);
1681 add_header (ct, np, vp);
1682 break;
1683
1684 case CE_QUOTED:
1685 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1686 adios (NULL, "internal error, invalid encoding");
1687
1688 np = add (ENCODING_FIELD, NULL);
1689 vp = concat (" ", "quoted-printable", "\n", NULL);
1690 add_header (ct, np, vp);
1691 break;
1692
1693 case CE_BASE64:
1694 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1695 adios (NULL, "internal error, invalid encoding");
1696
1697 np = add (ENCODING_FIELD, NULL);
1698 vp = concat (" ", "base64", "\n", NULL);
1699 add_header (ct, np, vp);
1700 break;
1701
1702 case CE_BINARY:
1703 if (ct->c_type == CT_MESSAGE)
1704 adios (NULL, "internal error, invalid encoding");
1705
1706 np = add (ENCODING_FIELD, NULL);
1707 vp = concat (" ", "binary", "\n", NULL);
1708 add_header (ct, np, vp);
1709 break;
1710
1711 default:
1712 adios (NULL, "unknown transfer encoding in content");
1713 break;
1714 }
1715
1716 /*
1717 * Additional content specific header processing
1718 */
1719 switch (ct->c_type) {
1720 case CT_MULTIPART:
1721 {
1722 struct multipart *m;
1723 struct part *part;
1724
1725 m = (struct multipart *) ct->c_ctparams;
1726 for (part = m->mp_parts; part; part = part->mp_next) {
1727 CT p;
1728
1729 p = part->mp_part;
1730 build_headers (p, header_encoding);
1731 }
1732 }
1733 break;
1734
1735 case CT_MESSAGE:
1736 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1737 struct exbody *e;
1738
1739 e = (struct exbody *) ct->c_ctparams;
1740 build_headers (e->eb_content, header_encoding);
1741 }
1742 break;
1743
1744 default:
1745 /* Nothing to do */
1746 break;
1747 }
1748
1749 return OK;
1750 }
1751
1752
1753 static char nib2b64[0x40+1] =
1754 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1755
1756 static char *
1757 calculate_digest (CT ct, int asciiP)
1758 {
1759 int cc;
1760 char *vp, *op;
1761 unsigned char *dp;
1762 unsigned char digest[16];
1763 unsigned char outbuf[25];
1764 MD5_CTX mdContext;
1765 CE ce = &ct->c_cefile;
1766 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1767 FILE *in;
1768
1769 /* open content */
1770 if ((in = fopen (infilename, "r")) == NULL)
1771 adios (infilename, "unable to open for reading");
1772
1773 /* Initialize md5 context */
1774 MD5Init (&mdContext);
1775
1776 /* calculate md5 message digest */
1777 if (asciiP) {
1778 char *bufp = NULL;
1779 size_t buflen;
1780 ssize_t gotlen;
1781 while ((gotlen = getline(&bufp, &buflen, in)) != -1) {
1782 char c, *cp;
1783
1784 cp = bufp + gotlen - 1;
1785 if ((c = *cp) == '\n')
1786 gotlen--;
1787
1788 MD5Update (&mdContext, (unsigned char *) bufp,
1789 (unsigned int) gotlen);
1790
1791 if (c == '\n')
1792 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1793 }
1794 } else {
1795 char buffer[BUFSIZ];
1796 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1797 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1798 }
1799
1800 /* md5 finalization. Write digest and zero md5 context */
1801 MD5Final (digest, &mdContext);
1802
1803 /* close content */
1804 fclose (in);
1805
1806 /* print debugging info */
1807 if (debugsw) {
1808 unsigned char *ep;
1809
1810 fprintf (stderr, "MD5 digest=");
1811 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1812 dp < ep; dp++)
1813 fprintf (stderr, "%02x", *dp & 0xff);
1814 fprintf (stderr, "\n");
1815 }
1816
1817 /* encode the digest using base64 */
1818 for (dp = digest, op = (char *) outbuf,
1819 cc = sizeof(digest) / sizeof(digest[0]);
1820 cc > 0; cc -= 3, op += 4) {
1821 unsigned long bits;
1822 char *bp;
1823
1824 bits = (*dp++ & 0xff) << 16;
1825 if (cc > 1) {
1826 bits |= (*dp++ & 0xff) << 8;
1827 if (cc > 2)
1828 bits |= *dp++ & 0xff;
1829 }
1830
1831 for (bp = op + 4; bp > op; bits >>= 6)
1832 *--bp = nib2b64[bits & 0x3f];
1833 if (cc < 3) {
1834 *(op + 3) = '=';
1835 if (cc < 2)
1836 *(op + 2) = '=';
1837 }
1838 }
1839
1840 /* null terminate string */
1841 outbuf[24] = '\0';
1842
1843 /* now make copy and return string */
1844 vp = concat (" ", outbuf, "\n", NULL);
1845 return vp;
1846 }
1847
1848 /*
1849 * Set things up for the content structure for file "filename" that
1850 * we want to attach
1851 */
1852
1853 static void
1854 setup_attach_content(CT ct, char *filename)
1855 {
1856 char *type, *simplename = r1bindex(filename, '/');
1857 struct str2init *s2i;
1858 PM pm;
1859
1860 if (! (type = mime_type(filename))) {
1861 adios(NULL, "Unable to determine MIME type of \"%s\"", filename);
1862 }
1863
1864 /*
1865 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1866 * since we're just feeding it a MIME type we have to add those ourself.
1867 * Map that to a valid content-type label and call any initialization
1868 * function.
1869 */
1870
1871 if (get_ctinfo(type, ct, 0) == NOTOK)
1872 done(1);
1873
1874 free(type);
1875
1876 for (s2i = str2cts; s2i->si_key; s2i++)
1877 if (strcasecmp(ct->c_ctinfo.ci_type, s2i->si_key) == 0)
1878 break;
1879 if (!s2i->si_key && !uprf(ct->c_ctinfo.ci_type, "X-"))
1880 s2i++;
1881
1882 /*
1883 * Make sure the type isn't incompatible with what we can handle
1884 */
1885
1886 switch (ct->c_type = s2i->si_val) {
1887 case CT_MULTIPART:
1888 adios (NULL, "multipart types must be specified by mhbuild directives");
1889 /* NOTREACHED */
1890
1891 case CT_MESSAGE:
1892 if (strcasecmp(ct->c_ctinfo.ci_subtype, "partial") == 0)
1893 adios(NULL, "Sorry, %s/%s isn't supported", ct->c_ctinfo.ci_type,
1894 ct->c_ctinfo.ci_subtype);
1895 if (strcasecmp(ct->c_ctinfo.ci_subtype, "external-body") == 0)
1896 adios(NULL, "external-body messages must be specified "
1897 "by mhbuild directives");
1898 /* Fall through */
1899
1900 default:
1901 /*
1902 * This sets the subtype, if it's significant
1903 */
1904 if ((ct->c_ctinitfnx = s2i->si_init))
1905 (*ct->c_ctinitfnx)(ct);
1906 break;
1907 }
1908
1909 /*
1910 * Feed in a few attributes; specifically, the name attribute, the
1911 * content-description, and the content-disposition.
1912 */
1913
1914 for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
1915 if (strcasecmp(pm->pm_name, "name") == 0) {
1916 if (pm->pm_value)
1917 free(pm->pm_value);
1918 pm->pm_value = getcpy(simplename);
1919 break;
1920 }
1921 }
1922
1923 if (pm == NULL)
1924 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
1925 "name", simplename, 0);
1926
1927 ct->c_descr = getcpy(simplename);
1928 ct->c_descr = add("\n", ct->c_descr);
1929 ct->c_cefile.ce_file = getcpy(filename);
1930
1931 /*
1932 * If it's a text/calendar, we need to make sure it's an inline,
1933 * otherwise it won't work with some calendar programs. Otherwise
1934 * assume attachment
1935 */
1936
1937 if (strcasecmp(ct->c_ctinfo.ci_type, "text") == 0 &&
1938 strcasecmp(ct->c_ctinfo.ci_subtype, "calendar") == 0) {
1939 ct->c_dispo_type = getcpy("inline");
1940 } else {
1941 ct->c_dispo_type = getcpy("attachment");
1942 }
1943
1944 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", simplename, 0);
1945 }