workarounds, I do not understand why it segfault, it should *NOT*
[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             if (*value)
173                 m_strrtrim(*value);
174             return p;
175
176           case '\\':
177             p += 1 + (p[1] != 0);
178             break;
179
180           case '=':
181             if (!*value) {
182                 *p++ = '\0';
183                 p = *value = vskipspaces(p);
184                 continue;
185             }
186             /* falltrhrough */
187
188           default:
189             p++;
190             break;
191         }
192     }
193 }
194
195 static inline void
196 parse_field_error(const char *type, const char *filename, int line)
197 {
198     mutt_error(_("Improperly formated entry for type %s in \"%s\" line %d"),
199                type, filename, line);
200 }
201
202 /* rfc1524 mailcap file is of the format:
203  * base/type; command; extradefs
204  * type can be * for matching all
205  * base with no /type is an implicit wild
206  * command contains a %s for the filename to pass, default to pipe on stdin
207  * extradefs are of the form:
208  *  def1="definition"; def2="define \;";
209  * line wraps with a \ at the end of the line
210  * # for comments
211  */
212 static int
213 rfc1524_mailcap_parse(BODY *a, const char *filename, const char *type,
214                       rfc1524_entry *entry, int opt)
215 {
216     FILE *fp;
217     char *buf = NULL;
218     ssize_t buflen;
219     char *ch;
220     char *field, *value;
221     int found = FALSE;
222     int copiousoutput;
223     int composecommand;
224     int editcommand;
225     int printcommand;
226     int btlen;
227     int line = 0;
228
229     /* find length of basetype */
230     if ((ch = strchr (type, '/')) == NULL)
231         return FALSE;
232     btlen = ch - type;
233
234     fp = fopen(filename, "r");
235     if (!fp)
236         goto error;
237
238     while (!found && (buf = mutt_read_line(buf, &buflen, fp, &line)) != NULL) {
239         /* ignore comments */
240         if (*buf == '#')
241             continue;
242
243         /* check type */
244         ch = parse_field(buf, &field, &value);
245         if (ascii_strcasecmp(field, type)
246             && (ascii_strncasecmp(field, type, btlen)
247                 || (buf[btlen] != 0 && m_strcmp(buf + btlen, "/*"))))
248             continue;
249
250         /* next field is the viewcommand */
251         ch = parse_field(ch, &field, &value);
252         if (entry)
253             entry->command = m_strdup(field);
254
255         /* parse the optional fields */
256         found = TRUE;
257         copiousoutput = FALSE;
258         composecommand = FALSE;
259         editcommand = FALSE;
260         printcommand = FALSE;
261
262         while (*ch) {
263             ch = parse_field(ch, &field, &value);
264
265             switch (mime_which_token(field, -1)) {
266 #define DO_CASE(token, field, expr)                                             \
267               case token:                                                       \
268                 if (value) {                                                    \
269                     if (entry)                                                  \
270                         m_strreplace(&entry->field, value);                     \
271                     expr;                                                       \
272                 } else {                                                        \
273                     parse_field_error(type, filename, line);                    \
274                 }                                                               \
275                 break;
276
277               case MIME_NEEDSTERMINAL:
278                 if (entry)
279                     entry->needsterminal = TRUE;
280                 break;
281
282               case MIME_COPIOUSOUTPUT:
283                 copiousoutput = TRUE;
284                 if (entry)
285                     entry->copiousoutput = TRUE;
286                 break;
287
288                 DO_CASE(MIME_COMPOSETYPED, composecommand, composecommand = TRUE);
289                 DO_CASE(MIME_COMPOSE, composecommand, composecommand = TRUE);
290                 DO_CASE(MIME_PRINT, printcommand, printcommand = TRUE);
291                 DO_CASE(MIME_EDIT, editcommand, editcommand = TRUE);
292                 DO_CASE(MIME_NAMETEMPLATE, nametemplate, );
293                 DO_CASE(MIME_X_CONVERT, convert, );
294
295               case MIME_TEST:
296                 /*
297                  * This routine executes the given test command to determine
298                  * if this is the right entry.  a non-zero exit code means
299                  * test failed
300                  */
301                 if (value) {
302                     ssize_t len   = m_strlen(value) + STRING;
303                     char *testcmd = p_new(char, len);
304
305                     m_strcpy(testcmd, len, value);
306                     rfc1524_expand_command(a, a->filename, type, testcmd, len);
307                     found = !mutt_system(testcmd);
308                     p_delete(&testcmd);
309                 } else {
310                     parse_field_error(type, filename, line);
311                 }
312                 break;
313
314               default:
315                 break;
316 #undef DO_CASE
317             }
318         }
319
320         if (opt == M_AUTOVIEW) {
321             if (!copiousoutput)
322                 found = FALSE;
323         }
324         else if (opt == M_COMPOSE) {
325             if (!composecommand)
326                 found = FALSE;
327         }
328         else if (opt == M_EDIT) {
329             if (!editcommand)
330                 found = FALSE;
331         }
332         else if (opt == M_PRINT) {
333             if (!printcommand)
334                 found = FALSE;
335         }
336
337         if (!found && entry) {
338             rfc1524_entry_wipe(entry);
339             rfc1524_entry_init(entry);
340         }
341     }                           /* while (!found && (buf = mutt_read_line ())) */
342     m_fclose(&fp);
343
344   error:
345     p_delete(&buf);
346     return found;
347 }
348
349
350 /************** READ MARK **********************/
351
352 /*
353  * rfc1524_mailcap_lookup attempts to find the given type in the
354  * list of mailcap files.  On success, this returns the entry information
355  * in *entry, and returns 1.  On failure (not found), returns 0.
356  * If entry == NULL just return 1 if the given type is found.
357  */
358 int rfc1524_mailcap_lookup (BODY * a, char *type, rfc1524_entry * entry,
359                             int opt)
360 {
361   char path[_POSIX_PATH_MAX];
362   int x;
363   int found = FALSE;
364   char *curr = MailcapPath;
365
366   /* rfc1524 specifies that a path of mailcap files should be searched.
367    * joy.  They say 
368    * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
369    * and overriden by the MAILCAPS environment variable, and, just to be nice, 
370    * we'll make it specifiable in .muttrc
371    */
372   if (!curr || !*curr) {
373     mutt_error _("No mailcap path specified");
374
375     return 0;
376   }
377
378   mutt_check_lookup_list (a, type, STRING);
379
380   while (!found && *curr) {
381     x = 0;
382     while (*curr && *curr != ':' && x < ssizeof (path) - 1) {
383       path[x++] = *curr;
384       curr++;
385     }
386     if (*curr)
387       curr++;
388
389     if (!x)
390       continue;
391
392     path[x] = '\0';
393     mutt_expand_path (path, sizeof (path));
394
395     found = rfc1524_mailcap_parse (a, path, type, entry, opt);
396   }
397
398   if (entry && !found)
399     mutt_error (_("mailcap entry for type %s not found"), type);
400
401   return found;
402 }
403
404
405 /* This routine will create a _temporary_ filename matching the
406  * name template given if this needs to be done.
407  * 
408  * Please note that only the last path element of the
409  * template and/or the old file name will be used for the
410  * comparison and the temporary file name.
411  * 
412  * Returns 0 if oldfile is fine as is.
413  * Returns 1 if newfile specified
414  */
415
416 int rfc1524_expand_filename (char *nametemplate,
417                              char *oldfile, char *newfile, ssize_t nflen)
418 {
419   int i, j, k, ps, r;
420   char *s;
421   short lmatch = 0, rmatch = 0;
422   char left[_POSIX_PATH_MAX];
423   char right[_POSIX_PATH_MAX];
424
425   newfile[0] = 0;
426
427   /* first, ignore leading path components.
428    */
429
430   if (nametemplate && (s = strrchr (nametemplate, '/')))
431     nametemplate = s + 1;
432
433   if (oldfile && (s = strrchr (oldfile, '/')))
434     oldfile = s + 1;
435
436   if (!nametemplate) {
437     if (oldfile)
438       m_strcpy(newfile, nflen, oldfile);
439   }
440   else if (!oldfile) {
441     m_file_fmt(newfile, nflen, nametemplate, "mutt");
442   } else {                        /* oldfile && nametemplate */
443
444
445     /* first, compare everything left from the "%s" 
446      * (if there is one).
447      */
448
449     lmatch = 1;
450     ps = 0;
451     for (i = 0; nametemplate[i]; i++) {
452       if (nametemplate[i] == '%' && nametemplate[i + 1] == 's') {
453         ps = 1;
454         break;
455       }
456
457       /* note that the following will _not_ read beyond oldfile's end. */
458
459       if (lmatch && nametemplate[i] != oldfile[i])
460         lmatch = 0;
461     }
462
463     if (ps) {
464
465       /* If we had a "%s", check the rest. */
466
467       /* now, for the right part: compare everything right from 
468        * the "%s" to the final part of oldfile.
469        * 
470        * The logic here is as follows:
471        * 
472        * - We start reading from the end.
473        * - There must be a match _right_ from the "%s",
474        *   thus the i + 2.  
475        * - If there was a left hand match, this stuff
476        *   must not be counted again.  That's done by the
477        *   condition (j >= (lmatch ? i : 0)).
478        */
479
480       rmatch = 1;
481
482       for (r = 0, j = m_strlen(oldfile) - 1, k =
483            m_strlen(nametemplate) - 1;
484            j >= (lmatch ? i : 0) && k >= i + 2; j--, k--) {
485         if (nametemplate[k] != oldfile[j]) {
486           rmatch = 0;
487           break;
488         }
489       }
490
491       /* Now, check if we had a full match. */
492
493       if (k >= i + 2)
494         rmatch = 0;
495
496       if (lmatch)
497         *left = 0;
498       else
499         m_strncpy(left, sizeof(left), nametemplate, i);
500
501       if (rmatch)
502         *right = 0;
503       else
504         m_strcpy(right, sizeof(right), nametemplate + i + 2);
505
506       snprintf (newfile, nflen, "%s%s%s", left, oldfile, right);
507     }
508     else {
509       /* no "%s" in the name template. */
510       m_strcpy(newfile, nflen, nametemplate);
511     }
512   }
513
514   mutt_adv_mktemp (NULL, newfile, nflen);
515
516   return !(rmatch && lmatch);
517 }
518