]>
diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
07358cd356b0c6a69119a718253e6207d7d25751
3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
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.
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.
20 #include <h/signals.h>
26 #include <h/mhparse.h>
29 #ifdef HAVE_SYS_TIME_H
30 # include <sys/time.h>
40 extern int contentidsw
;
43 extern int rcachesw
; /* mhcachesbr.c */
44 extern int wcachesw
; /* mhcachesbr.c */
47 * Directory to place tmp files. This must
48 * be set before these routines are called.
54 static char prefix
[] = "----- =_aaaaaaaaaa";
57 * Maximum size of URL token in message/external-body
60 #define MAXURLTOKEN 40
64 void content_error (char *, CT
, char *, ...);
67 int find_cache (CT
, int, int *, char *, char *, int);
70 void free_ctinfo (CT
);
71 void free_encoding (CT
, int);
76 CT
build_mime (char *, int);
81 static int init_decoded_content (CT
);
82 static char *fgetstr (char *, int, FILE *);
83 static int user_content (FILE *, char *, char *, CT
*);
84 static void set_id (CT
, int);
85 static int compose_content (CT
);
86 static int scan_content (CT
);
87 static int build_headers (CT
);
88 static char *calculate_digest (CT
, int);
91 static unsigned char directives_stack
[32];
92 static unsigned int directives_index
;
94 static int do_direct(void)
96 return directives_stack
[directives_index
];
99 static void directive_onoff(int onoff
)
101 if (directives_index
>= sizeof(directives_stack
) - 1) {
102 fprintf(stderr
, "mhbuild: #on/off overflow, continuing\n");
105 directives_stack
[++directives_index
] = onoff
;
108 static void directive_init(int onoff
)
110 directives_index
= 0;
111 directives_stack
[0] = onoff
;
114 static void directive_pop(void)
116 if (directives_index
> 0)
119 fprintf(stderr
, "mhbuild: #pop underflow, continuing\n");
123 * Main routine for translating composition file
124 * into valid MIME message. It translates the draft
125 * into a content structure (actually a tree of content
126 * structures). This message then can be manipulated
127 * in various ways, including being output via
132 build_mime (char *infile
, int directives
)
135 char buf
[BUFSIZ
], name
[NAMESZ
];
141 m_getfld_state_t gstate
= 0;
143 directive_init(directives
);
145 umask (~m_gmprot ());
147 /* open the composition draft */
148 if ((in
= fopen (infile
, "r")) == NULL
)
149 adios (infile
, "unable to open for reading");
152 * Allocate space for primary (outside) content
154 if ((ct
= (CT
) calloc (1, sizeof(*ct
))) == NULL
)
155 adios (NULL
, "out of memory");
158 * Allocate structure for handling decoded content
159 * for this part. We don't really need this, but
160 * allocate it to remain consistent.
162 init_decoded_content (ct
);
165 * Parse some of the header fields in the composition
166 * draft into the linked list of header fields for
167 * the new MIME message.
169 m_getfld_track_filepos (&gstate
, in
);
170 for (compnum
= 1;;) {
171 int bufsz
= sizeof buf
;
172 switch (state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
)) {
177 /* abort if draft has Mime-Version header field */
178 if (!strcasecmp (name
, VRSN_FIELD
))
179 adios (NULL
, "draft shouldn't contain %s: field", VRSN_FIELD
);
181 /* abort if draft has Content-Transfer-Encoding header field */
182 if (!strcasecmp (name
, ENCODING_FIELD
))
183 adios (NULL
, "draft shouldn't contain %s: field", ENCODING_FIELD
);
185 /* ignore any Content-Type fields in the header */
186 if (!strcasecmp (name
, TYPE_FIELD
)) {
187 while (state
== FLDPLUS
) {
189 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
194 /* get copies of the buffers */
195 np
= add (name
, NULL
);
196 vp
= add (buf
, NULL
);
198 /* if necessary, get rest of field */
199 while (state
== FLDPLUS
) {
201 state
= m_getfld (&gstate
, name
, buf
, &bufsz
, in
);
202 vp
= add (buf
, vp
); /* add to previous value */
205 /* Now add the header data to the list */
206 add_header (ct
, np
, vp
);
209 /* if this wasn't the last header field, then continue */
213 adios (NULL
, "draft has empty body -- no directives!");
217 fseek (in
, (long) (-strlen (buf
)), SEEK_CUR
);
222 adios (NULL
, "message format error in component #%d", compnum
);
225 adios (NULL
, "getfld() returned %d", state
);
229 m_getfld_state_destroy (&gstate
);
232 * Now add the MIME-Version header field
233 * to the list of header fields.
235 np
= add (VRSN_FIELD
, NULL
);
236 vp
= concat (" ", VRSN_VALUE
, "\n", NULL
);
237 add_header (ct
, np
, vp
);
240 * We initally assume we will find multiple contents in the
241 * draft. So create a multipart/mixed content to hold everything.
242 * We can remove this later, if it is not needed.
244 if (get_ctinfo ("multipart/mixed", ct
, 0) == NOTOK
)
246 ct
->c_type
= CT_MULTIPART
;
247 ct
->c_subtype
= MULTI_MIXED
;
248 ct
->c_file
= add (infile
, NULL
);
250 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
251 adios (NULL
, "out of memory");
252 ct
->c_ctparams
= (void *) m
;
256 * read and parse the composition file
257 * and the directives it contains.
259 while (fgetstr (buf
, sizeof(buf
) - 1, in
)) {
263 if (user_content (in
, infile
, buf
, &p
) == DONE
) {
264 admonish (NULL
, "ignoring spurious #end");
270 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
271 adios (NULL
, "out of memory");
278 * close the composition draft since
279 * it's not needed any longer.
283 /* check if any contents were found */
285 adios (NULL
, "no content directives found");
288 * If only one content was found, then remove and
289 * free the outer multipart content.
291 if (!m
->mp_parts
->mp_next
) {
294 p
= m
->mp_parts
->mp_part
;
295 m
->mp_parts
->mp_part
= NULL
;
297 /* move header fields */
298 p
->c_first_hf
= ct
->c_first_hf
;
299 p
->c_last_hf
= ct
->c_last_hf
;
300 ct
->c_first_hf
= NULL
;
301 ct
->c_last_hf
= NULL
;
310 * Fill out, or expand directives. Parse and execute
311 * commands specified by profile composition strings.
313 compose_content (ct
);
315 if ((cp
= strchr(prefix
, 'a')) == NULL
)
316 adios (NULL
, "internal error(4)");
319 * Scan the contents. Choose a transfer encoding, and
320 * check if prefix for multipart boundary clashes with
321 * any of the contents.
323 while (scan_content (ct
) == NOTOK
) {
328 adios (NULL
, "giving up trying to find a unique delimiter string");
334 /* Build the rest of the header field structures */
342 * Set up structures for placing unencoded
343 * content when building parts.
347 init_decoded_content (CT ct
)
349 ct
->c_ceopenfnx
= open7Bit
; /* since unencoded */
350 ct
->c_ceclosefnx
= close_encoding
;
351 ct
->c_cesizefnx
= NULL
; /* since unencoded */
358 fgetstr (char *s
, int n
, FILE *stream
)
364 for (ep
= (cp
= s
) + o_n
; cp
< ep
; ) {
367 if (!fgets (cp
, n
, stream
))
368 return (cp
!= s
? s
: NULL
);
370 if (cp
== s
&& *cp
!= '#')
373 cp
+= (i
= strlen (cp
)) - 1;
374 if (i
<= 1 || *cp
-- != '\n' || *cp
!= '\\')
380 if (strcmp(s
, "#on\n") == 0) {
382 } else if (strcmp(s
, "#off\n") == 0) {
384 } else if (strcmp(s
, "#pop\n") == 0) {
396 * Parse the composition draft for text and directives.
397 * Do initial setup of Content structure.
401 user_content (FILE *in
, char *file
, char *buf
, CT
*ctp
)
409 struct str2init
*s2i
;
414 if (buf
[0] == '\n' || (do_direct() && strcmp (buf
, "#\n") == 0)) {
419 /* allocate basic Content structure */
420 if ((ct
= (CT
) calloc (1, sizeof(*ct
))) == NULL
)
421 adios (NULL
, "out of memory");
424 /* allocate basic structure for handling decoded content */
425 init_decoded_content (ct
);
432 * Handle inline text. Check if line
433 * is one of the following forms:
435 * 1) doesn't begin with '#' (implicit directive)
436 * 2) begins with "##" (implicit directive)
437 * 3) begins with "#<"
439 if (!do_direct() || buf
[0] != '#' || buf
[1] == '#' || buf
[1] == '<') {
443 char content
[BUFSIZ
];
447 cp
= m_mktemp2(NULL
, invo_name
, NULL
, &out
);
448 if (cp
== NULL
) adios("mhbuildsbr", "unable to create temporary file");
450 /* use a temp file to collect the plain text lines */
451 ce
->ce_file
= add (cp
, NULL
);
454 if (do_direct() && (buf
[0] == '#' && buf
[1] == '<')) {
455 strncpy (content
, buf
+ 2, sizeof(content
));
462 /* the directive is implicit */
463 strncpy (content
, "text/plain", sizeof(content
));
465 strncpy (buffer
, (!do_direct() || buf
[0] != '#') ? buf
: buf
+ 1, sizeof(buffer
));
469 if (headers
>= 0 && do_direct() && uprf (buffer
, DESCR_FIELD
)
470 && buffer
[i
= strlen (DESCR_FIELD
)] == ':') {
474 ct
->c_descr
= add (buffer
+ i
+ 1, ct
->c_descr
);
475 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
476 adios (NULL
, "end-of-file after %s: field in plaintext", DESCR_FIELD
);
484 adios (NULL
, "#-directive after %s: field in plaintext", DESCR_FIELD
);
492 if (headers
>= 0 && do_direct() && uprf (buffer
, DISPO_FIELD
)
493 && buffer
[i
= strlen (DISPO_FIELD
)] == ':') {
497 ct
->c_dispo
= add (buffer
+ i
+ 1, ct
->c_dispo
);
498 if (!fgetstr (buffer
, sizeof(buffer
) - 1, in
))
499 adios (NULL
, "end-of-file after %s: field in plaintext", DISPO_FIELD
);
507 adios (NULL
, "#-directive after %s: field in plaintext", DISPO_FIELD
);
515 if (headers
!= 1 || buffer
[0] != '\n')
521 if ((cp
= fgetstr (buffer
, sizeof(buffer
) - 1, in
)) == NULL
)
523 if (do_direct() && buffer
[0] == '#') {
526 if (buffer
[1] != '#')
528 for (cp
= (bp
= buffer
) + 1; *cp
; cp
++)
535 ct
->c_end
= ftell (out
);
538 /* parse content type */
539 if (get_ctinfo (content
, ct
, inlineD
) == NOTOK
)
542 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
543 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
545 if (!s2i
->si_key
&& !uprf (ci
->ci_type
, "X-"))
549 * check type specified (possibly implicitly)
551 switch (ct
->c_type
= s2i
->si_val
) {
553 if (!strcasecmp (ci
->ci_subtype
, "rfc822")) {
554 ct
->c_encoding
= CE_7BIT
;
559 adios (NULL
, "it doesn't make sense to define an in-line %s content",
560 ct
->c_type
== CT_MESSAGE
? "message" : "multipart");
565 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
566 (*ct
->c_ctinitfnx
) (ct
);
571 fseek (in
, pos
, SEEK_SET
);
576 * If we've reached this point, the next line
577 * must be some type of explicit directive.
580 /* check if directive is external-type */
581 extrnal
= (buf
[1] == '@');
583 /* parse directive */
584 if (get_ctinfo (buf
+ (extrnal
? 2 : 1), ct
, 1) == NOTOK
)
587 /* check directive against the list of MIME types */
588 for (s2i
= str2cts
; s2i
->si_key
; s2i
++)
589 if (!strcasecmp (ci
->ci_type
, s2i
->si_key
))
593 * Check if the directive specified a valid type.
594 * This will happen if it was one of the following forms:
601 adios (NULL
, "missing subtype in \"#%s\"", ci
->ci_type
);
603 switch (ct
->c_type
= s2i
->si_val
) {
605 adios (NULL
, "use \"#begin ... #end\" instead of \"#%s/%s\"",
606 ci
->ci_type
, ci
->ci_subtype
);
610 if (!strcasecmp (ci
->ci_subtype
, "partial"))
611 adios (NULL
, "sorry, \"#%s/%s\" isn't supported",
612 ci
->ci_type
, ci
->ci_subtype
);
613 if (!strcasecmp (ci
->ci_subtype
, "external-body"))
614 adios (NULL
, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
615 ci
->ci_type
, ci
->ci_subtype
);
618 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
619 ci
->ci_type
, ci
->ci_subtype
);
623 if ((ct
->c_ctinitfnx
= s2i
->si_init
))
624 (*ct
->c_ctinitfnx
) (ct
);
629 * #@type/subtype (external types directive)
636 adios (NULL
, "need external information for \"#@%s/%s\"",
637 ci
->ci_type
, ci
->ci_subtype
);
640 snprintf (buffer
, sizeof(buffer
), "message/external-body; %s", ci
->ci_magic
);
645 * Since we are using the current Content structure to
646 * hold information about the type of the external
647 * reference, we need to create another Content structure
648 * for the message/external-body to wrap it in.
650 if ((ct
= (CT
) calloc (1, sizeof(*ct
))) == NULL
)
651 adios (NULL
, "out of memory");
654 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
656 ct
->c_type
= CT_MESSAGE
;
657 ct
->c_subtype
= MESSAGE_EXTERNAL
;
659 if ((e
= (struct exbody
*) calloc (1, sizeof(*e
))) == NULL
)
660 adios (NULL
, "out of memory");
661 ct
->c_ctparams
= (void *) e
;
667 if (params_external (ct
, 1) == NOTOK
)
673 /* Handle [file] argument */
675 /* check if specifies command to execute */
676 if (*ci
->ci_magic
== '|' || *ci
->ci_magic
== '!') {
677 for (cp
= ci
->ci_magic
+ 1; isspace ((unsigned char) *cp
); cp
++)
680 adios (NULL
, "empty pipe command for #%s directive", ci
->ci_type
);
685 /* record filename of decoded contents */
686 ce
->ce_file
= ci
->ci_magic
;
687 if (access (ce
->ce_file
, R_OK
) == NOTOK
)
688 adios ("reading", "unable to access %s for", ce
->ce_file
);
689 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
690 ct
->c_end
= (long) st
.st_size
;
697 * No [file] argument, so check profile for
698 * method to compose content.
700 snprintf (buffer
, sizeof(buffer
), "%s-compose-%s/%s",
701 invo_name
, ci
->ci_type
, ci
->ci_subtype
);
702 if ((cp
= context_find (buffer
)) == NULL
|| *cp
== '\0') {
703 snprintf (buffer
, sizeof(buffer
), "%s-compose-%s", invo_name
, ci
->ci_type
);
704 if ((cp
= context_find (buffer
)) == NULL
|| *cp
== '\0') {
705 content_error (NULL
, ct
, "don't know how to compose content");
709 ci
->ci_magic
= add (cp
, NULL
);
714 adios (NULL
, "external definition not allowed for \"#%s\"", ci
->ci_type
);
718 * #forw [+folder] [msgs]
720 if (!strcasecmp (ci
->ci_type
, "forw")) {
722 char *folder
, *arguments
[MAXARGS
];
726 ap
= brkstring (ci
->ci_magic
, " ", "\n");
727 copyip (ap
, arguments
, MAXARGS
);
729 arguments
[0] = "cur";
734 /* search the arguments for a folder name */
735 for (ap
= arguments
; *ap
; ap
++) {
737 if (*cp
== '+' || *cp
== '@') {
739 adios (NULL
, "only one folder per #forw directive");
741 folder
= pluspath (cp
);
745 /* else, use the current folder */
747 folder
= add (getfolder (1), NULL
);
749 if (!(mp
= folder_read (folder
, 0)))
750 adios (NULL
, "unable to read folder %s", folder
);
751 for (ap
= arguments
; *ap
; ap
++) {
753 if (*cp
!= '+' && *cp
!= '@')
754 if (!m_convert (mp
, cp
))
761 * If there is more than one message to include, make this
762 * a content of type "multipart/digest" and insert each message
763 * as a subpart. If there is only one message, then make this
764 * a content of type "message/rfc822".
766 if (mp
->numsel
> 1) {
767 /* we are forwarding multiple messages */
768 if (get_ctinfo ("multipart/digest", ct
, 0) == NOTOK
)
770 ct
->c_type
= CT_MULTIPART
;
771 ct
->c_subtype
= MULTI_DIGEST
;
773 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
774 adios (NULL
, "out of memory");
775 ct
->c_ctparams
= (void *) m
;
778 for (msgnum
= mp
->lowsel
; msgnum
<= mp
->hghsel
; msgnum
++) {
779 if (is_selected(mp
, msgnum
)) {
784 if ((p
= (CT
) calloc (1, sizeof(*p
))) == NULL
)
785 adios (NULL
, "out of memory");
786 init_decoded_content (p
);
788 if (get_ctinfo ("message/rfc822", p
, 0) == NOTOK
)
790 p
->c_type
= CT_MESSAGE
;
791 p
->c_subtype
= MESSAGE_RFC822
;
793 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
794 pe
->ce_file
= add (buffer
, NULL
);
795 if (listsw
&& stat (pe
->ce_file
, &st
) != NOTOK
)
796 p
->c_end
= (long) st
.st_size
;
798 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
799 adios (NULL
, "out of memory");
806 /* we are forwarding one message */
807 if (get_ctinfo ("message/rfc822", ct
, 0) == NOTOK
)
809 ct
->c_type
= CT_MESSAGE
;
810 ct
->c_subtype
= MESSAGE_RFC822
;
813 snprintf (buffer
, sizeof(buffer
), "%s/%d", mp
->foldpath
, msgnum
);
814 ce
->ce_file
= add (buffer
, NULL
);
815 if (listsw
&& stat (ce
->ce_file
, &st
) != NOTOK
)
816 ct
->c_end
= (long) st
.st_size
;
819 folder_free (mp
); /* free folder/message structure */
826 if (!strcasecmp (ci
->ci_type
, "end")) {
833 * #begin [ alternative | parallel ]
835 if (!strcasecmp (ci
->ci_type
, "begin")) {
838 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
839 } else if (!strcasecmp (ci
->ci_magic
, "alternative")) {
840 vrsn
= MULTI_ALTERNATE
;
841 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
842 } else if (!strcasecmp (ci
->ci_magic
, "parallel")) {
843 vrsn
= MULTI_PARALLEL
;
844 cp
= SubMultiPart
[vrsn
- 1].kv_key
;
845 } else if (uprf (ci
->ci_magic
, "digest")) {
848 vrsn
= MULTI_UNKNOWN
;
853 snprintf (buffer
, sizeof(buffer
), "multipart/%s", cp
);
854 if (get_ctinfo (buffer
, ct
, 0) == NOTOK
)
856 ct
->c_type
= CT_MULTIPART
;
857 ct
->c_subtype
= vrsn
;
859 if ((m
= (struct multipart
*) calloc (1, sizeof(*m
))) == NULL
)
860 adios (NULL
, "out of memory");
861 ct
->c_ctparams
= (void *) m
;
864 while (fgetstr (buffer
, sizeof(buffer
) - 1, in
)) {
868 if (user_content (in
, file
, buffer
, &p
) == DONE
) {
870 adios (NULL
, "empty \"#begin ... #end\" sequence");
876 if ((part
= (struct part
*) calloc (1, sizeof(*part
))) == NULL
)
877 adios (NULL
, "out of memory");
882 admonish (NULL
, "premature end-of-file, missing #end");
889 adios (NULL
, "unknown directive \"#%s\"", ci
->ci_type
);
890 return NOTOK
; /* NOT REACHED */
895 set_id (CT ct
, int top
)
897 char contentid
[BUFSIZ
];
899 static time_t clock
= 0;
904 snprintf (contentid
, sizeof(contentid
), "%s\n", message_id (clock
, 1));
906 msgfmt
= getcpy(contentid
);
908 snprintf (contentid
, sizeof(contentid
), msgfmt
, top
? 0 : ++partno
);
909 ct
->c_id
= getcpy (contentid
);
914 * Fill out, or expand the various contents in the composition
915 * draft. Read-in any necessary files. Parse and execute any
916 * commands specified by profile composition strings.
920 compose_content (CT ct
)
922 CE ce
= &ct
->c_cefile
;
924 switch (ct
->c_type
) {
929 char partnam
[BUFSIZ
];
930 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
934 snprintf (partnam
, sizeof(partnam
), "%s.", ct
->c_partno
);
935 pp
= partnam
+ strlen (partnam
);
940 /* first, we call compose_content on all the subparts */
941 for (part
= m
->mp_parts
, partnum
= 1; part
; part
= part
->mp_next
, partnum
++) {
942 CT p
= part
->mp_part
;
944 sprintf (pp
, "%d", partnum
);
945 p
->c_partno
= add (partnam
, NULL
);
946 if (compose_content (p
) == NOTOK
)
951 * If the -rfc934mode switch is given, then check all
952 * the subparts of a multipart/digest. If they are all
953 * message/rfc822, then mark this content and all
954 * subparts with the rfc934 compatibility mode flag.
956 if (rfc934sw
&& ct
->c_subtype
== MULTI_DIGEST
) {
959 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
960 CT p
= part
->mp_part
;
962 if (p
->c_subtype
!= MESSAGE_RFC822
) {
967 ct
->c_rfc934
= is934
;
968 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
969 CT p
= part
->mp_part
;
971 if ((p
->c_rfc934
= is934
))
977 ct
->c_end
= (partnum
= strlen (prefix
) + 2) + 2;
981 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
)
982 ct
->c_end
+= part
->mp_part
->c_end
+ partnum
;
988 /* Nothing to do for type message */
992 * Discrete types (text/application/audio/image/video)
997 int i
, xstdout
, len
, buflen
;
999 char *vec
[4], buffer
[BUFSIZ
];
1001 CI ci
= &ct
->c_ctinfo
;
1004 if (!(cp
= ci
->ci_magic
))
1005 adios (NULL
, "internal error(5)");
1007 tfile
= m_mktemp2(NULL
, invo_name
, NULL
, NULL
);
1008 if (tfile
== NULL
) {
1009 adios("mhbuildsbr", "unable to create temporary file");
1011 ce
->ce_file
= add (tfile
, NULL
);
1016 /* Get buffer ready to go */
1019 buflen
= sizeof(buffer
);
1022 * Parse composition string into buffer
1024 for ( ; *cp
; cp
++) {
1029 /* insert parameters from directive */
1033 for (ap
= ci
->ci_attrs
, ep
= ci
->ci_values
; *ap
; ap
++, ep
++) {
1034 snprintf (bp
, buflen
, "%s%s=\"%s\"", s
, *ap
, *ep
);
1044 /* %f, and stdout is not-redirected */
1050 * insert temporary filename where
1051 * content should be written
1053 snprintf (bp
, buflen
, "%s", ce
->ce_file
);
1057 /* insert content subtype */
1058 strncpy (bp
, ci
->ci_subtype
, buflen
);
1062 /* insert character % */
1083 printf ("composing content %s/%s from command\n\t%s\n",
1084 ci
->ci_type
, ci
->ci_subtype
, buffer
);
1086 fflush (stdout
); /* not sure if need for -noverbose */
1093 if ((out
= fopen (ce
->ce_file
, "w")) == NULL
)
1094 adios (ce
->ce_file
, "unable to open for writing");
1096 for (i
= 0; (child_id
= fork()) == NOTOK
&& i
> 5; i
++)
1100 adios ("fork", "unable to fork");
1105 dup2 (fileno (out
), 1);
1106 close (fileno (out
));
1107 execvp ("/bin/sh", vec
);
1108 fprintf (stderr
, "unable to exec ");
1115 if (pidXwait(child_id
, NULL
))
1121 /* Check size of file */
1122 if (listsw
&& ct
->c_end
== 0L) {
1125 if (stat (ce
->ce_file
, &st
) != NOTOK
)
1126 ct
->c_end
= (long) st
.st_size
;
1138 * 1) choose a transfer encoding.
1139 * 2) check for clashes with multipart boundary string.
1140 * 3) for text content, figure out which character set is being used.
1142 * If there is a clash with one of the contents and the multipart boundary,
1143 * this function will exit with NOTOK. This will cause the scanning process
1144 * to be repeated with a different multipart boundary. It is possible
1145 * (although highly unlikely) that this scan will be repeated multiple times.
1149 scan_content (CT ct
)
1152 int check8bit
= 0, contains8bit
= 0; /* check if contains 8bit data */
1153 int checklinelen
= 0, linelen
= 0; /* check for long lines */
1154 int checkboundary
= 0, boundaryclash
= 0; /* check if clashes with multipart boundary */
1155 int checklinespace
= 0, linespace
= 0; /* check if any line ends with space */
1156 char *cp
= NULL
, buffer
[BUFSIZ
];
1157 struct text
*t
= NULL
;
1159 CE ce
= &ct
->c_cefile
;
1162 * handle multipart by scanning all subparts
1163 * and then checking their encoding.
1165 if (ct
->c_type
== CT_MULTIPART
) {
1166 struct multipart
*m
= (struct multipart
*) ct
->c_ctparams
;
1169 /* initially mark the domain of enclosing multipart as 7bit */
1170 ct
->c_encoding
= CE_7BIT
;
1172 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1173 CT p
= part
->mp_part
;
1175 if (scan_content (p
) == NOTOK
) /* choose encoding for subpart */
1178 /* if necessary, enlarge encoding for enclosing multipart */
1179 if (p
->c_encoding
== CE_BINARY
)
1180 ct
->c_encoding
= CE_BINARY
;
1181 if (p
->c_encoding
== CE_8BIT
&& ct
->c_encoding
!= CE_BINARY
)
1182 ct
->c_encoding
= CE_8BIT
;
1189 * Decide what to check while scanning this content.
1191 switch (ct
->c_type
) {
1195 if (ct
->c_subtype
== TEXT_PLAIN
) {
1204 case CT_APPLICATION
:
1216 /* don't check anything for message/external */
1217 if (ct
->c_subtype
== MESSAGE_EXTERNAL
)
1227 * Don't check anything for these types,
1228 * since we are forcing use of base64.
1238 * Scan the unencoded content
1240 if (check8bit
|| checklinelen
|| checklinespace
|| checkboundary
) {
1241 if ((in
= fopen (ce
->ce_file
, "r")) == NULL
)
1242 adios (ce
->ce_file
, "unable to open for reading");
1243 len
= strlen (prefix
);
1245 while (fgets (buffer
, sizeof(buffer
) - 1, in
)) {
1247 * Check for 8bit data.
1250 for (cp
= buffer
; *cp
; cp
++) {
1251 if (!isascii ((unsigned char) *cp
)) {
1253 check8bit
= 0; /* no need to keep checking */
1259 * Check line length.
1261 if (checklinelen
&& (strlen (buffer
) > CPERLIN
+ 1)) {
1263 checklinelen
= 0; /* no need to keep checking */
1267 * Check if line ends with a space.
1269 if (checklinespace
&& (cp
= buffer
+ strlen (buffer
) - 2) > buffer
&& isspace ((unsigned char) *cp
)) {
1271 checklinespace
= 0; /* no need to keep checking */
1275 * Check if content contains a line that clashes
1276 * with our standard boundary for multipart messages.
1278 if (checkboundary
&& buffer
[0] == '-' && buffer
[1] == '-') {
1279 for (cp
= buffer
+ strlen (buffer
) - 1; cp
>= buffer
; cp
--)
1280 if (!isspace ((unsigned char) *cp
))
1283 if (!strncmp(buffer
+ 2, prefix
, len
) && isdigit((unsigned char) buffer
[2 + len
])) {
1285 checkboundary
= 0; /* no need to keep checking */
1293 * Decide which transfer encoding to use.
1295 switch (ct
->c_type
) {
1298 * If the text content didn't specify a character
1299 * set, we need to figure out which one was used.
1301 t
= (struct text
*) ct
->c_ctparams
;
1302 if (t
->tx_charset
== CHARSET_UNSPECIFIED
) {
1303 CI ci
= &ct
->c_ctinfo
;
1306 for (ap
= ci
->ci_attrs
, ep
= ci
->ci_values
; *ap
; ap
++, ep
++)
1310 *ap
= concat ("charset=", write_charset_8bit(), NULL
);
1312 *ap
= add ("charset=us-ascii", NULL
);
1314 t
->tx_charset
= CHARSET_SPECIFIED
;
1316 cp
= strchr(*ap
++, '=');
1322 if (contains8bit
|| linelen
|| linespace
|| checksw
)
1323 ct
->c_encoding
= CE_QUOTED
;
1325 ct
->c_encoding
= CE_7BIT
;
1328 case CT_APPLICATION
:
1329 /* For application type, use base64, except when postscript */
1330 if (contains8bit
|| linelen
|| linespace
|| checksw
)
1331 ct
->c_encoding
= (ct
->c_subtype
== APPLICATION_POSTSCRIPT
)
1332 ? CE_QUOTED
: CE_BASE64
;
1334 ct
->c_encoding
= CE_7BIT
;
1338 ct
->c_encoding
= CE_7BIT
;
1344 /* For audio, image, and video contents, just use base64 */
1345 ct
->c_encoding
= CE_BASE64
;
1349 return (boundaryclash
? NOTOK
: OK
);
1354 * Scan the content structures, and build header
1355 * fields that will need to be output into the
1360 build_headers (CT ct
)
1362 int cc
, mailbody
, extbody
, len
;
1364 char *np
, *vp
, buffer
[BUFSIZ
];
1365 CI ci
= &ct
->c_ctinfo
;
1368 * If message is type multipart, then add the multipart
1369 * boundary to the list of attribute/value pairs.
1371 if (ct
->c_type
== CT_MULTIPART
) {
1373 static int level
= 0; /* store nesting level */
1377 snprintf (buffer
, sizeof(buffer
), "boundary=%s%d", prefix
, level
++);
1378 cp
= strchr(*ap
++ = add (buffer
, NULL
), '=');
1385 * Skip the output of Content-Type, parameters, content
1386 * description and disposition, and Content-ID if the
1387 * content is of type "message" and the rfc934 compatibility
1388 * flag is set (which means we are inside multipart/digest
1389 * and the switch -rfc934mode was given).
1391 if (ct
->c_type
== CT_MESSAGE
&& ct
->c_rfc934
)
1395 * output the content type and subtype
1397 np
= add (TYPE_FIELD
, NULL
);
1398 vp
= concat (" ", ci
->ci_type
, "/", ci
->ci_subtype
, NULL
);
1400 /* keep track of length of line */
1401 len
= strlen (TYPE_FIELD
) + strlen (ci
->ci_type
)
1402 + strlen (ci
->ci_subtype
) + 3;
1404 extbody
= ct
->c_type
== CT_MESSAGE
&& ct
->c_subtype
== MESSAGE_EXTERNAL
;
1405 mailbody
= extbody
&& ((struct exbody
*) ct
->c_ctparams
)->eb_body
;
1408 * Append the attribute/value pairs to
1409 * the end of the Content-Type line.
1411 for (ap
= ci
->ci_attrs
, ep
= ci
->ci_values
; *ap
; ap
++, ep
++) {
1412 if (mailbody
&& !strcasecmp (*ap
, "body"))
1419 * According to RFC 2017, if we have a URL longer than 40 characters
1420 * we have to break it across multiple lines
1423 if (extbody
&& strcasecmp (*ap
, "url") == 0) {
1426 /* 7 here refers to " url=\"\"" */
1427 if (len
+ 1 + (cc
= (min(MAXURLTOKEN
, strlen(value
)) + 7)) >=
1429 vp
= add ("\n\t", vp
);
1436 vp
= add ("url=\"", vp
);
1439 while (strlen(value
) > MAXURLTOKEN
) {
1440 strncpy(buffer
, value
, MAXURLTOKEN
);
1441 buffer
[MAXURLTOKEN
] = '\0';
1442 vp
= add (buffer
, vp
);
1443 vp
= add ("\n\t", vp
);
1444 value
+= MAXURLTOKEN
;
1448 vp
= add (value
, vp
);
1449 vp
= add ("\"", vp
);
1450 len
+= strlen(value
) + 1;
1454 snprintf (buffer
, sizeof(buffer
), "%s=\"%s\"", *ap
, *ep
);
1455 if (len
+ 1 + (cc
= strlen (buffer
)) >= CPERLIN
) {
1456 vp
= add ("\n\t", vp
);
1462 vp
= add (buffer
, vp
);
1467 * Append any RFC-822 comment to the end of
1468 * the Content-Type line.
1470 if (ci
->ci_comment
) {
1471 snprintf (buffer
, sizeof(buffer
), "(%s)", ci
->ci_comment
);
1472 if (len
+ 1 + (cc
= 2 + strlen (ci
->ci_comment
)) >= CPERLIN
) {
1473 vp
= add ("\n\t", vp
);
1479 vp
= add (buffer
, vp
);
1482 vp
= add ("\n", vp
);
1483 add_header (ct
, np
, vp
);
1486 * output the Content-ID, unless disabled by -nocontentid
1488 if (contentidsw
&& ct
->c_id
) {
1489 np
= add (ID_FIELD
, NULL
);
1490 vp
= concat (" ", ct
->c_id
, NULL
);
1491 add_header (ct
, np
, vp
);
1495 * output the Content-Description
1498 np
= add (DESCR_FIELD
, NULL
);
1499 vp
= concat (" ", ct
->c_descr
, NULL
);
1500 add_header (ct
, np
, vp
);
1504 * output the Content-Disposition
1507 np
= add (DISPO_FIELD
, NULL
);
1508 vp
= concat (" ", ct
->c_dispo
, NULL
);
1509 add_header (ct
, np
, vp
);
1514 * If this is the internal content structure for a
1515 * "message/external", then we are done with the
1516 * headers (since it has no body).
1522 * output the Content-MD5
1525 np
= add (MD5_FIELD
, NULL
);
1526 vp
= calculate_digest (ct
, (ct
->c_encoding
== CE_QUOTED
) ? 1 : 0);
1527 add_header (ct
, np
, vp
);
1531 * output the Content-Transfer-Encoding
1533 switch (ct
->c_encoding
) {
1535 /* Nothing to output */
1539 if (ct
->c_type
== CT_MESSAGE
)
1540 adios (NULL
, "internal error, invalid encoding");
1542 np
= add (ENCODING_FIELD
, NULL
);
1543 vp
= concat (" ", "8bit", "\n", NULL
);
1544 add_header (ct
, np
, vp
);
1548 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1549 adios (NULL
, "internal error, invalid encoding");
1551 np
= add (ENCODING_FIELD
, NULL
);
1552 vp
= concat (" ", "quoted-printable", "\n", NULL
);
1553 add_header (ct
, np
, vp
);
1557 if (ct
->c_type
== CT_MESSAGE
|| ct
->c_type
== CT_MULTIPART
)
1558 adios (NULL
, "internal error, invalid encoding");
1560 np
= add (ENCODING_FIELD
, NULL
);
1561 vp
= concat (" ", "base64", "\n", NULL
);
1562 add_header (ct
, np
, vp
);
1566 if (ct
->c_type
== CT_MESSAGE
)
1567 adios (NULL
, "internal error, invalid encoding");
1569 np
= add (ENCODING_FIELD
, NULL
);
1570 vp
= concat (" ", "binary", "\n", NULL
);
1571 add_header (ct
, np
, vp
);
1575 adios (NULL
, "unknown transfer encoding in content");
1580 * Additional content specific header processing
1582 switch (ct
->c_type
) {
1585 struct multipart
*m
;
1588 m
= (struct multipart
*) ct
->c_ctparams
;
1589 for (part
= m
->mp_parts
; part
; part
= part
->mp_next
) {
1599 if (ct
->c_subtype
== MESSAGE_EXTERNAL
) {
1602 e
= (struct exbody
*) ct
->c_ctparams
;
1603 build_headers (e
->eb_content
);
1616 static char nib2b64
[0x40+1] =
1617 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1620 calculate_digest (CT ct
, int asciiP
)
1623 char buffer
[BUFSIZ
], *vp
, *op
;
1625 unsigned char digest
[16];
1626 unsigned char outbuf
[25];
1628 CE ce
= &ct
->c_cefile
;
1629 char *infilename
= ce
->ce_file
? ce
->ce_file
: ct
->c_file
;
1633 if ((in
= fopen (infilename
, "r")) == NULL
)
1634 adios (infilename
, "unable to open for reading");
1636 /* Initialize md5 context */
1637 MD5Init (&mdContext
);
1639 /* calculate md5 message digest */
1641 while (fgets (buffer
, sizeof(buffer
) - 1, in
)) {
1644 cp
= buffer
+ strlen (buffer
) - 1;
1645 if ((c
= *cp
) == '\n')
1648 MD5Update (&mdContext
, (unsigned char *) buffer
,
1649 (unsigned int) strlen (buffer
));
1652 MD5Update (&mdContext
, (unsigned char *) "\r\n", 2);
1655 while ((cc
= fread (buffer
, sizeof(*buffer
), sizeof(buffer
), in
)) > 0)
1656 MD5Update (&mdContext
, (unsigned char *) buffer
, (unsigned int) cc
);
1659 /* md5 finalization. Write digest and zero md5 context */
1660 MD5Final (digest
, &mdContext
);
1665 /* print debugging info */
1669 fprintf (stderr
, "MD5 digest=");
1670 for (ep
= (dp
= digest
) + sizeof(digest
) / sizeof(digest
[0]);
1672 fprintf (stderr
, "%02x", *dp
& 0xff);
1673 fprintf (stderr
, "\n");
1676 /* encode the digest using base64 */
1677 for (dp
= digest
, op
= (char *) outbuf
,
1678 cc
= sizeof(digest
) / sizeof(digest
[0]);
1679 cc
> 0; cc
-= 3, op
+= 4) {
1683 bits
= (*dp
++ & 0xff) << 16;
1685 bits
|= (*dp
++ & 0xff) << 8;
1687 bits
|= *dp
++ & 0xff;
1690 for (bp
= op
+ 4; bp
> op
; bits
>>= 6)
1691 *--bp
= nib2b64
[bits
& 0x3f];
1699 /* null terminate string */
1702 /* now make copy and return string */
1703 vp
= concat (" ", outbuf
, "\n", NULL
);