f82cc6c35094fe806cbc5f5dfff054a210631c4e
[mmh] / uip / mhoutsbr.c
1 /*
2 ** mhoutsbr.c -- routines to output MIME messages
3 **            -- given a Content structure
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 #include <h/mh.h>
11 #include <fcntl.h>
12 #include <h/signals.h>
13 #include <h/md5.h>
14 #include <errno.h>
15 #include <signal.h>
16 #include <h/tws.h>
17 #include <h/mime.h>
18 #include <h/mhparse.h>
19
20 extern int ebcdicsw;
21
22 static char ebcdicsafe[0x100] = {
23         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
24         0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
25         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
26         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
27         0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
28         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
29         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
30         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
31         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
32         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
33         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
34         0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
35         0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
36         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
37         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
38         0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
39         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
40         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
41         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
42         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
44         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
46         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
47         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
48         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
49         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
50         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
51         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
52         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
53         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
54         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
55 };
56
57 static char nib2b64[0x40+1] =
58         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
59
60 /*
61 ** prototypes
62 */
63 int output_message(CT, char *);
64 int output_message_fp(CT, FILE *, char *);
65 int writeBase64aux(FILE *, FILE *);
66
67 /*
68 ** static prototypes
69 */
70 static int output_content(CT, FILE *);
71 static void output_headers(CT, FILE *);
72 static int writeExternalBody(CT, FILE *);
73 static int write8Bit(CT, FILE *);
74 static int writeQuoted(CT, FILE *);
75 static int writeBase64(CT, FILE *);
76
77
78 /*
79 ** Main routine to output a MIME message contained
80 ** in a Content structure, to a file.  Any necessary
81 ** transfer encoding is added.
82 */
83
84 int
85 output_message_fp(CT ct, FILE *fp, char *file)
86 {
87         if (output_content(ct, fp) == NOTOK)
88                 return NOTOK;
89
90         if (fflush(fp)) {
91                 advise((file?file:"<FILE*>"), "error writing to");
92                 return NOTOK;
93         }
94         return OK;
95 }
96
97 int
98 output_message(CT ct, char *file)
99 {
100         FILE *fp;
101         int status;
102
103         if ((fp = fopen(file, "w")) == NULL) {
104                 advise(file, "unable to open for writing");
105                 return NOTOK;
106         }
107         status = output_message_fp(ct, fp, file);
108         fclose(fp);
109         return status;
110 }
111
112
113 /*
114 ** Output a Content structure to a file.
115 */
116
117 static int
118 output_content(CT ct, FILE *out)
119 {
120         int result = 0;
121         CI ci = &ct->c_ctinfo;
122
123         /*
124         ** Output all header fields for this content
125         */
126         output_headers(ct, out);
127
128         /*
129         ** If this is the internal content structure for a
130         ** "message/external", then we are done with the
131         ** headers (since it has no body).
132         */
133         if (ct->c_ctexbody)
134                 return OK;
135
136         /*
137         ** Now output the content bodies.
138         */
139         switch (ct->c_type) {
140         case CT_MULTIPART:
141         {
142                 struct multipart *m;
143                 struct part *part;
144
145                 if (ct->c_rfc934)
146                         putc('\n', out);
147
148                 m = (struct multipart *) ct->c_ctparams;
149                 for (part = m->mp_parts; part; part = part->mp_next) {
150                         CT p = part->mp_part;
151
152                         fprintf(out, "\n--%s\n", ci->ci_values[0]);
153                         if (output_content(p, out) == NOTOK)
154                                 return NOTOK;
155                 }
156                 fprintf(out, "\n--%s--\n", ci->ci_values[0]);
157         }
158         break;
159
160         case CT_MESSAGE:
161                 putc('\n', out);
162                 if (ct->c_subtype == MESSAGE_EXTERNAL) {
163                         struct exbody *e;
164
165                         e = (struct exbody *) ct->c_ctparams;
166                         if (output_content(e->eb_content, out) == NOTOK)
167                                 return NOTOK;
168
169                         /* output phantom body for access-type "mail-server" */
170                         if (e->eb_body)
171                                 writeExternalBody(ct, out);
172                 } else {
173                         result = write8Bit(ct, out);
174                 }
175                 break;
176
177         /*
178         ** Handle discrete types (text/application/audio/image/video)
179         */
180         default:
181                 switch (ct->c_encoding) {
182                 case CE_7BIT:
183                         putc('\n', out);
184                         result = write8Bit(ct, out);
185                         break;
186
187                 case CE_8BIT:
188                         putc('\n', out);
189                         result = write8Bit(ct, out);
190                         break;
191
192                 case CE_QUOTED:
193                         putc('\n', out);
194                         result = writeQuoted(ct, out);
195                         break;
196
197                 case CE_BASE64:
198                         putc('\n', out);
199                         result = writeBase64(ct, out);
200                         break;
201
202                 case CE_BINARY:
203                         advise(NULL, "can't handle binary transfer encoding in content");
204                         result = NOTOK;
205                         break;
206
207                 default:
208                         advise(NULL, "unknown transfer encoding in content");
209                         result = NOTOK;
210                         break;
211                 }
212                 break;
213         }
214
215         return result;
216 }
217
218
219 /*
220 ** Output all the header fields for a content
221 */
222
223 static void
224 output_headers(CT ct, FILE *out)
225 {
226         HF hp;
227
228         hp = ct->c_first_hf;
229         while (hp) {
230                 fprintf(out, "%s:%s", hp->name, hp->value);
231                 hp = hp->next;
232         }
233 }
234
235
236 /*
237 ** Write the phantom body for access-type "mail-server".
238 */
239
240 static int
241 writeExternalBody(CT ct, FILE *out)
242 {
243         char **ap, **ep, *cp;
244         struct exbody *e = (struct exbody *) ct->c_ctparams;
245
246         putc('\n', out);
247         for (cp = e->eb_body; *cp; cp++) {
248                 CT ct2 = e->eb_content;
249                 CI ci2 = &ct2->c_ctinfo;
250
251                 if (*cp == '\\') {
252                         switch (*++cp) {
253                         case 'I':
254                                 if (ct2->c_id) {
255                                         char *dp = trimcpy(ct2->c_id);
256
257                                         fputs(dp, out);
258                                         free(dp);
259                                 }
260                                 continue;
261
262                         case 'N':
263                                 for (ap = ci2->ci_attrs, ep = ci2->ci_values;
264                                                 *ap; ap++, ep++)
265                                         if (!mh_strcasecmp(*ap, "name")) {
266                                                 fprintf(out, "%s", *ep);
267                                                 break;
268                                         }
269                                 continue;
270
271                         case 'T':
272                                 fprintf(out, "%s/%s", ci2->ci_type,
273                                                 ci2->ci_subtype);
274                                 for (ap = ci2->ci_attrs, ep = ci2->ci_values;
275                                                 *ap; ap++, ep++)
276                                         fprintf(out, "; %s=\"%s\"", *ap, *ep);
277                                 continue;
278
279                         case 'n':
280                                 putc('\n', out);
281                                 continue;
282
283                         case 't':
284                                 putc('\t', out);
285                                 continue;
286
287                         case '\0':
288                                 cp--;
289                                 break;
290
291                         case '\\':
292                         case '"':
293                                 break;
294
295                         default:
296                                 putc('\\', out);
297                                 break;
298                         }
299                 }
300                 putc(*cp, out);
301         }
302         putc('\n', out);
303
304         return OK;
305 }
306
307
308 /*
309 ** Output a content without any transfer encoding
310 */
311
312 static int
313 write8Bit(CT ct, FILE *out)
314 {
315         int fd;
316         char c, *file, buffer[BUFSIZ];
317         CE ce = ct->c_cefile;
318
319         file = NULL;
320         if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
321                 return NOTOK;
322
323         c = '\n';
324         while (fgets(buffer, sizeof(buffer) - 1, ce->ce_fp)) {
325                 c = buffer[strlen(buffer) - 1];
326                 fputs(buffer, out);
327         }
328         if (c != '\n')
329                 putc('\n', out);
330
331         (*ct->c_ceclosefnx) (ct);
332         return OK;
333 }
334
335
336 /*
337 ** Output a content using quoted-printable
338 */
339
340 static int
341 writeQuoted(CT ct, FILE *out)
342 {
343         int fd;
344         char *cp, *file;
345         char c, buffer[BUFSIZ];
346         CE ce = ct->c_cefile;
347
348         file = NULL;
349         if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
350                 return NOTOK;
351
352         while (fgets(buffer, sizeof(buffer) - 1, ce->ce_fp)) {
353                 int n;
354
355                 cp = buffer + strlen(buffer) - 1;
356                 if ((c = *cp) == '\n')
357                         *cp = '\0';
358
359                 if (strncmp(cp = buffer, "From ", sizeof("From ") - 1) == 0) {
360                         fprintf(out, "=%02X", *cp++ & 0xff);
361                         n = 3;
362                 } else {
363                         n = 0;
364                 }
365                 for (; *cp; cp++) {
366                         if (n > CPERLIN - 3) {
367                                 fputs("=\n", out);
368                                 n = 0;
369                         }
370
371                         switch (*cp) {
372                         case ' ':
373                         case '\t':
374                                 putc(*cp, out);
375                                 n++;
376                                 break;
377
378                         default:
379                                 if (*cp < '!' || *cp > '~' || (ebcdicsw && !ebcdicsafe[*cp & 0xff]))
380                                         goto three_print;
381                                 putc(*cp, out);
382                                 n++;
383                                 break;
384
385                         case '=':
386 three_print:
387                                 fprintf(out, "=%02X", *cp & 0xff);
388                                 n += 3;
389                                 break;
390                         }
391                 }
392
393                 if (c == '\n') {
394                         if (cp > buffer && (*--cp == ' ' || *cp == '\t'))
395                                 fputs("=\n", out);
396
397                         putc('\n', out);
398                 } else {
399                         fputs("=\n", out);
400                 }
401         }
402
403         (*ct->c_ceclosefnx) (ct);
404         return OK;
405 }
406
407
408 /*
409 ** Output a content using base64
410 */
411
412 static int
413 writeBase64(CT ct, FILE *out)
414 {
415         int fd, result;
416         char *file;
417         CE ce = ct->c_cefile;
418
419         file = NULL;
420         if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
421                 return NOTOK;
422
423         result = writeBase64aux(ce->ce_fp, out);
424         (*ct->c_ceclosefnx) (ct);
425         return result;
426 }
427
428
429 int
430 writeBase64aux(FILE *in, FILE *out)
431 {
432         int cc, n;
433         char inbuf[3];
434
435         n = BPERLIN;
436         while ((cc = fread(inbuf, sizeof(*inbuf), sizeof(inbuf), in)) > 0) {
437                 unsigned long bits;
438                 char *bp;
439                 char outbuf[4];
440
441                 if (cc < sizeof(inbuf)) {
442                         inbuf[2] = 0;
443                         if (cc < sizeof(inbuf) - 1)
444                                 inbuf[1] = 0;
445                 }
446                 bits = (inbuf[0] & 0xff) << 16;
447                 bits |= (inbuf[1] & 0xff) << 8;
448                 bits |= inbuf[2] & 0xff;
449
450                 for (bp = outbuf + sizeof(outbuf); bp > outbuf; bits >>= 6)
451                         *--bp = nib2b64[bits & 0x3f];
452                 if (cc < sizeof(inbuf)) {
453                         outbuf[3] = '=';
454                         if (cc < sizeof inbuf - 1)
455                                 outbuf[2] = '=';
456                 }
457
458                 fwrite(outbuf, sizeof(*outbuf), sizeof(outbuf), out);
459
460                 if (cc < sizeof(inbuf)) {
461                         putc('\n', out);
462                         return OK;
463                 }
464
465                 if (--n <= 0) {
466                         n = BPERLIN;
467                         putc('\n', out);
468                 }
469         }
470         if (n != BPERLIN)
471                 putc('\n', out);
472
473         return OK;
474 }