code simplifications.
[apps/madmutt.git] / lib-mime / rfc1524.c
1 /*
2  *  This program is free software; you can redistribute it and/or modify
3  *  it under the terms of the GNU General Public License as published by
4  *  the Free Software Foundation; either version 2 of the License, or (at
5  *  your option) any later version.
6  *
7  *  This program is distributed in the hope that it will be useful, but
8  *  WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
15  *  MA 02110-1301, USA.
16  *
17  *  Copyright © 2006 Pierre Habouzit
18  */
19 /*
20  * Copyright notice from original mutt:
21  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
22  *
23  * This file is part of mutt-ng, see http://www.muttng.org/.
24  * It's licensed under the GNU General Public License,
25  * please see the file GPL in the top level source directory.
26  */
27
28 /* 
29  * rfc1524 defines a format for the Multimedia Mail Configuration, which
30  * is the standard mailcap file format under Unix which specifies what 
31  * external programs should be used to view/compose/edit multimedia files
32  * based on content type.
33  *
34  * This file contains various functions for implementing a fair subset of 
35  * rfc1524.
36  */
37
38 #include <lib-lib/lib-lib.h>
39
40 #include <lib-sys/unix.h>
41
42 #include "mutt.h"
43 #include "mime.h"
44 #include "attach.h"
45
46 void rfc1524_entry_wipe(rfc1524_entry *p)
47 {
48     p_delete(&p->command);
49     p_delete(&p->testcommand);
50     p_delete(&p->composecommand);
51     p_delete(&p->composetypecommand);
52     p_delete(&p->editcommand);
53     p_delete(&p->printcommand);
54     p_delete(&p->nametemplate);
55     p_delete(&p->convert);
56 }
57
58 /* returns 1 if Mutt can't display this type of data, 0 otherwise */
59 int rfc1524_mailcap_isneeded(BODY * m)
60 {
61     int tok;
62
63     switch (m->type) {
64       case TYPEMULTIPART:
65       case TYPEMESSAGE:
66         return 0;
67
68       case TYPEAPPLICATION:
69         return !(mutt_is_application_pgp(m) || mutt_is_application_smime(m));
70
71       case TYPETEXT:
72         tok = mime_which_token(m->subtype, -1);
73         if (tok == MIME_PLAIN
74         ||  tok == MIME_RFC822_HEADERS
75         ||  tok == MIME_ENRICHED)
76             return 0;
77         break;
78     }
79
80     return 1;
81 }
82
83 /* The command semantics include the following:
84  * %s is the filename that contains the mail body data
85  * %t is the content type, like text/plain
86  * %{parameter} is replaced by the parameter value from the content-type field
87  * \% is %
88  * Unsupported rfc1524 parameters: these would probably require some doing
89  * by mutt, and can probably just be done by piping the message to metamail
90  * %n is the integer number of sub-parts in the multipart
91  * %F is "content-type filename" repeated for each sub-part
92  *
93  * In addition, this function returns a 0 if the command works on a file,
94  * and 1 if the command works on a pipe.
95  */
96 int rfc1524_expand_command(BODY *a, const char *filename, const char *mtype,
97                            char *command, int clen)
98 {
99     int x = 0, y = 0;
100     int needspipe = TRUE;
101     char buf[LONG_STRING];
102     char type[LONG_STRING];
103
104     m_strcpy(type, sizeof(type), mtype);
105
106     if (option(OPTMAILCAPSANITIZE))
107         mutt_sanitize_filename(type, 0);
108
109     while (command[x] && x < clen && y < ssizeof(buf)) {
110         switch (command[x]) {
111           case '\\':
112             x++;
113             buf[y++] = command[x++];
114             break;
115
116           case '%':
117             x++;
118             if (command[x] == '{') {
119                 char param[STRING];
120                 char pval[STRING];
121                 int z = 0;
122
123                 x++;
124                 while (command[x] && command[x] != '}' && z < ssizeof (param))
125                     param[z++] = command[x++];
126                 param[z] = '\0';
127
128                 m_strcpy(pval, sizeof(pval),
129                          parameter_getval(a->parameter, param));
130
131                 if (option(OPTMAILCAPSANITIZE))
132                     mutt_sanitize_filename(pval, 0);
133
134                 y += mutt_quote_filename(buf + y, sizeof(buf) - y, pval);
135             }
136
137             if (command[x] == 's' && filename) {
138                 y += mutt_quote_filename(buf + y, sizeof(buf) - y, filename);
139                 needspipe = FALSE;
140             }
141
142             if (command[x] == 't') {
143                 y += mutt_quote_filename(buf + y, sizeof(buf) - y, type);
144             }
145             x++;
146             break;
147
148           default:
149             buf[y++] = command[x++];
150             break;
151         }
152     }
153
154     buf[y] = '\0';
155     m_strcpy(command, clen, buf);
156
157     return needspipe;
158 }
159
160 static char *parse_field(char *p, char **field, char **value)
161 {
162     p = vskipspaces(p);
163     *field = p;
164     *value = NULL;
165
166     for (;;) {
167         switch (*p) {
168           case ';':
169             *p++ = '\0';
170           case '\0':
171             m_strrtrim(*field);
172             m_strrtrim(*value);
173             return p;
174
175           case '\\':
176             p += 1 + (p[1] != 0);
177             break;
178
179           case '=':
180             if (!*value) {
181                 *p++ = '\0';
182                 p = *value = vskipspaces(p);
183                 continue;
184             }
185             /* falltrhrough */
186
187           default:
188             p++;
189             break;
190         }
191     }
192 }
193
194 static inline void
195 parse_field_error(const char *type, const char *filename, int line)
196 {
197     mutt_error(_("Improperly formated entry for type %s in \"%s\" line %d"),
198                type, filename, line);
199 }
200
201 /* rfc1524 mailcap file is of the format:
202  * base/type; command; extradefs
203  * type can be * for matching all
204  * base with no /type is an implicit wild
205  * command contains a %s for the filename to pass, default to pipe on stdin
206  * extradefs are of the form:
207  *  def1="definition"; def2="define \;";
208  * line wraps with a \ at the end of the line
209  * # for comments
210  */
211 static int
212 rfc1524_mailcap_parse(BODY *a, const char *filename, const char *type,
213                       rfc1524_entry *entry, int opt)
214 {
215     FILE *fp;
216     char *buf = NULL;
217     ssize_t buflen;
218     char *ch;
219     char *field, *value;
220     int found = FALSE;
221     int copiousoutput;
222     int composecommand;
223     int editcommand;
224     int printcommand;
225     int btlen;
226     int line = 0;
227
228     /* find length of basetype */
229     if ((ch = strchr (type, '/')) == NULL)
230         return FALSE;
231     btlen = ch - type;
232
233     fp = fopen(filename, "r");
234     if (!fp)
235         goto error;
236
237     while (!found && (buf = mutt_read_line(buf, &buflen, fp, &line)) != NULL) {
238         /* ignore comments */
239         if (*buf == '#')
240             continue;
241
242         /* check type */
243         ch = parse_field(buf, &field, &value);
244         if (ascii_strcasecmp(field, type)
245             && (ascii_strncasecmp(field, type, btlen)
246                 || (buf[btlen] != 0 && m_strcmp(buf + btlen, "/*"))))
247             continue;
248
249         /* next field is the viewcommand */
250         ch = parse_field(ch, &field, &value);
251         if (entry)
252             entry->command = m_strdup(field);
253
254         /* parse the optional fields */
255         found = TRUE;
256         copiousoutput = FALSE;
257         composecommand = FALSE;
258         editcommand = FALSE;
259         printcommand = FALSE;
260
261         while (*ch) {
262             ch = parse_field(ch, &field, &value);
263
264             switch (mime_which_token(field, -1)) {
265 #define DO_CASE(token, field, expr)                                             \
266               case token:                                                       \
267                 if (value) {                                                    \
268                     if (entry)                                                  \
269                         m_strreplace(&entry->field, value);                     \
270                     expr;                                                       \
271                 } else {                                                        \
272                     parse_field_error(type, filename, line);                    \
273                 }                                                               \
274                 break;
275
276               case MIME_NEEDSTERMINAL:
277                 if (entry)
278                     entry->needsterminal = TRUE;
279                 break;
280
281               case MIME_COPIOUSOUTPUT:
282                 copiousoutput = TRUE;
283                 if (entry)
284                     entry->copiousoutput = TRUE;
285                 break;
286
287                 DO_CASE(MIME_COMPOSETYPED, composecommand, composecommand = TRUE);
288                 DO_CASE(MIME_COMPOSE, composecommand, composecommand = TRUE);
289                 DO_CASE(MIME_PRINT, printcommand, printcommand = TRUE);
290                 DO_CASE(MIME_EDIT, editcommand, editcommand = TRUE);
291                 DO_CASE(MIME_NAMETEMPLATE, nametemplate, );
292                 DO_CASE(MIME_X_CONVERT, convert, );
293
294               case MIME_TEST:
295                 /*
296                  * This routine executes the given test command to determine
297                  * if this is the right entry.  a non-zero exit code means
298                  * test failed
299                  */
300                 if (value) {
301                     ssize_t len   = m_strlen(value) + STRING;
302                     char *testcmd = p_new(char, len);
303
304                     m_strcpy(testcmd, len, value);
305                     rfc1524_expand_command(a, a->filename, type, testcmd, len);
306                     found = !mutt_system(testcmd);
307                     p_delete(&testcmd);
308                 } else {
309                     parse_field_error(type, filename, line);
310                 }
311                 break;
312
313               default:
314                 break;
315 #undef DO_CASE
316             }
317         }
318
319         if (opt == M_AUTOVIEW) {
320             if (!copiousoutput)
321                 found = FALSE;
322         }
323         else if (opt == M_COMPOSE) {
324             if (!composecommand)
325                 found = FALSE;
326         }
327         else if (opt == M_EDIT) {
328             if (!editcommand)
329                 found = FALSE;
330         }
331         else if (opt == M_PRINT) {
332             if (!printcommand)
333                 found = FALSE;
334         }
335
336         if (!found && entry) {
337             rfc1524_entry_wipe(entry);
338             rfc1524_entry_init(entry);
339         }
340     }                           /* while (!found && (buf = mutt_read_line ())) */
341     m_fclose(&fp);
342
343   error:
344     p_delete(&buf);
345     return found;
346 }
347
348
349 /************** READ MARK **********************/
350
351 /*
352  * rfc1524_mailcap_lookup attempts to find the given type in the
353  * list of mailcap files.  On success, this returns the entry information
354  * in *entry, and returns 1.  On failure (not found), returns 0.
355  * If entry == NULL just return 1 if the given type is found.
356  */
357 int rfc1524_mailcap_lookup (BODY * a, char *type, rfc1524_entry * entry,
358                             int opt)
359 {
360   char path[_POSIX_PATH_MAX];
361   int x;
362   int found = FALSE;
363   char *curr = MailcapPath;
364
365   /* rfc1524 specifies that a path of mailcap files should be searched.
366    * joy.  They say 
367    * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
368    * and overriden by the MAILCAPS environment variable, and, just to be nice, 
369    * we'll make it specifiable in .muttrc
370    */
371   if (!curr || !*curr) {
372     mutt_error _("No mailcap path specified");
373
374     return 0;
375   }
376
377   mutt_check_lookup_list (a, type, STRING);
378
379   while (!found && *curr) {
380     x = 0;
381     while (*curr && *curr != ':' && x < ssizeof (path) - 1) {
382       path[x++] = *curr;
383       curr++;
384     }
385     if (*curr)
386       curr++;
387
388     if (!x)
389       continue;
390
391     path[x] = '\0';
392     mutt_expand_path (path, sizeof (path));
393
394     found = rfc1524_mailcap_parse (a, path, type, entry, opt);
395   }
396
397   if (entry && !found)
398     mutt_error (_("mailcap entry for type %s not found"), type);
399
400   return found;
401 }
402
403
404 /* This routine will create a _temporary_ filename matching the
405  * name template given if this needs to be done.
406  * 
407  * Please note that only the last path element of the
408  * template and/or the old file name will be used for the
409  * comparison and the temporary file name.
410  * 
411  * Returns 0 if oldfile is fine as is.
412  * Returns 1 if newfile specified
413  */
414
415 int rfc1524_expand_filename (char *nametemplate,
416                              char *oldfile, char *newfile, ssize_t nflen)
417 {
418   int i, j, k, ps, r;
419   char *s;
420   short lmatch = 0, rmatch = 0;
421   char left[_POSIX_PATH_MAX];
422   char right[_POSIX_PATH_MAX];
423
424   newfile[0] = 0;
425
426   /* first, ignore leading path components.
427    */
428
429   if (nametemplate && (s = strrchr (nametemplate, '/')))
430     nametemplate = s + 1;
431
432   if (oldfile && (s = strrchr (oldfile, '/')))
433     oldfile = s + 1;
434
435   if (!nametemplate) {
436     if (oldfile)
437       m_strcpy(newfile, nflen, oldfile);
438   }
439   else if (!oldfile) {
440     m_file_fmt(newfile, nflen, nametemplate, "mutt");
441   } else {                        /* oldfile && nametemplate */
442
443
444     /* first, compare everything left from the "%s" 
445      * (if there is one).
446      */
447
448     lmatch = 1;
449     ps = 0;
450     for (i = 0; nametemplate[i]; i++) {
451       if (nametemplate[i] == '%' && nametemplate[i + 1] == 's') {
452         ps = 1;
453         break;
454       }
455
456       /* note that the following will _not_ read beyond oldfile's end. */
457
458       if (lmatch && nametemplate[i] != oldfile[i])
459         lmatch = 0;
460     }
461
462     if (ps) {
463
464       /* If we had a "%s", check the rest. */
465
466       /* now, for the right part: compare everything right from 
467        * the "%s" to the final part of oldfile.
468        * 
469        * The logic here is as follows:
470        * 
471        * - We start reading from the end.
472        * - There must be a match _right_ from the "%s",
473        *   thus the i + 2.  
474        * - If there was a left hand match, this stuff
475        *   must not be counted again.  That's done by the
476        *   condition (j >= (lmatch ? i : 0)).
477        */
478
479       rmatch = 1;
480
481       for (r = 0, j = m_strlen(oldfile) - 1, k =
482            m_strlen(nametemplate) - 1;
483            j >= (lmatch ? i : 0) && k >= i + 2; j--, k--) {
484         if (nametemplate[k] != oldfile[j]) {
485           rmatch = 0;
486           break;
487         }
488       }
489
490       /* Now, check if we had a full match. */
491
492       if (k >= i + 2)
493         rmatch = 0;
494
495       if (lmatch)
496         *left = 0;
497       else
498         m_strncpy(left, sizeof(left), nametemplate, i);
499
500       if (rmatch)
501         *right = 0;
502       else
503         m_strcpy(right, sizeof(right), nametemplate + i + 2);
504
505       snprintf (newfile, nflen, "%s%s%s", left, oldfile, right);
506     }
507     else {
508       /* no "%s" in the name template. */
509       m_strcpy(newfile, nflen, nametemplate);
510     }
511   }
512
513   mutt_adv_mktemp (NULL, newfile, nflen);
514
515   return !(rmatch && lmatch);
516 }
517