Andreas Krennmair:
[apps/madmutt.git] / rfc1524.c
1 /*
2  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
3  * 
4  *     This program is free software; you can redistribute it and/or modify
5  *     it under the terms of the GNU General Public License as published by
6  *     the Free Software Foundation; either version 2 of the License, or
7  *     (at your option) any later version.
8  * 
9  *     This program is distributed in the hope that it will be useful,
10  *     but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *     GNU General Public License for more details.
13  * 
14  *     You should have received a copy of the GNU General Public License
15  *     along with this program; if not, write to the Free Software
16  *     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
17  */ 
18
19 /* 
20  * rfc1524 defines a format for the Multimedia Mail Configuration, which
21  * is the standard mailcap file format under Unix which specifies what 
22  * external programs should be used to view/compose/edit multimedia files
23  * based on content type.
24  *
25  * This file contains various functions for implementing a fair subset of 
26  * rfc1524.
27  */
28
29 #include "mutt.h"
30 #include "rfc1524.h"
31
32 #include <string.h>
33 #include <stdlib.h>
34 #include <ctype.h>
35
36 #include <sys/stat.h>
37 #include <sys/wait.h>
38 #include <errno.h>
39 #include <unistd.h>
40
41 /* The command semantics include the following:
42  * %s is the filename that contains the mail body data
43  * %t is the content type, like text/plain
44  * %{parameter} is replaced by the parameter value from the content-type field
45  * \% is %
46  * Unsupported rfc1524 parameters: these would probably require some doing
47  * by mutt, and can probably just be done by piping the message to metamail
48  * %n is the integer number of sub-parts in the multipart
49  * %F is "content-type filename" repeated for each sub-part
50  *
51  * In addition, this function returns a 0 if the command works on a file,
52  * and 1 if the command works on a pipe.
53  */
54 int rfc1524_expand_command (BODY *a, char *filename, char *_type,
55     char *command, int clen)
56 {
57   int x=0,y=0;
58   int needspipe = TRUE;
59   char buf[LONG_STRING];
60   char type[LONG_STRING];
61   
62   strfcpy (type, _type, sizeof (type));
63   
64   if (option (OPTMAILCAPSANITIZE))
65     mutt_sanitize_filename (type, 0);
66
67   while (command[x] && x<clen && y<sizeof(buf)) 
68   {
69     if (command[x] == '\\') {
70       x++;
71       buf[y++] = command[x++];
72     }
73     else if (command[x] == '%') 
74     {
75       x++;
76       if (command[x] == '{') 
77       {
78         char param[STRING];
79         char pvalue[STRING];
80         char *_pvalue;
81         int z = 0;
82
83         x++;
84         while (command[x] && command[x] != '}' && z<sizeof(param))
85           param[z++] = command[x++];
86         param[z] = '\0';
87         
88         _pvalue = mutt_get_parameter (param, a->parameter);
89         strfcpy (pvalue, NONULL(_pvalue), sizeof (pvalue));
90         if (option (OPTMAILCAPSANITIZE))
91           mutt_sanitize_filename (pvalue, 0);
92         
93         y += mutt_quote_filename (buf + y, sizeof (buf) - y, pvalue);
94       }
95       else if (command[x] == 's' && filename != NULL)
96       {
97         y += mutt_quote_filename (buf + y, sizeof (buf) - y, filename);
98         needspipe = FALSE;
99       }
100       else if (command[x] == 't')
101       {
102         y += mutt_quote_filename (buf + y, sizeof (buf) - y, type);
103       }
104       x++;
105     }
106     else
107       buf[y++] = command[x++];
108   }
109   buf[y] = '\0';
110   strfcpy (command, buf, clen);
111
112   return needspipe;
113 }
114
115 /* NUL terminates a rfc 1524 field,
116  * returns start of next field or NULL */
117 static char *get_field (char *s)
118 {
119   char *ch;
120
121   if (!s)
122     return NULL;
123
124   while ((ch = strpbrk (s, ";\\")) != NULL)
125   {
126     if (*ch == '\\')
127     {
128       s = ch + 1;
129       if (*s)
130         s++;
131     }
132     else
133     {
134       *ch++ = 0;
135       SKIPWS (ch);
136       break;
137     }
138   }
139   mutt_remove_trailing_ws (s);
140   return ch;
141 }
142
143 static int get_field_text (char *field, char **entry,
144                            char *type, char *filename, int line)
145 {
146   field = mutt_skip_whitespace (field);
147   if (*field == '=')
148   {
149     if (entry)
150     {
151       field++;
152       field = mutt_skip_whitespace (field);
153       mutt_str_replace (entry, field);
154     }
155     return 1;
156   }
157   else 
158   {
159     mutt_error (_("Improperly formated entry for type %s in \"%s\" line %d"),
160                 type, filename, line);
161     return 0;
162   }
163 }
164
165 static int rfc1524_mailcap_parse (BODY *a,
166                                   char *filename,
167                                   char *type, 
168                                   rfc1524_entry *entry,
169                                   int opt)
170 {
171   FILE *fp;
172   char *buf = NULL;
173   size_t buflen;
174   char *ch;
175   char *field;
176   int found = FALSE;
177   int copiousoutput;
178   int composecommand;
179   int editcommand;
180   int printcommand;
181   int btlen;
182   int line = 0;
183
184   /* rfc1524 mailcap file is of the format:
185    * base/type; command; extradefs
186    * type can be * for matching all
187    * base with no /type is an implicit wild
188    * command contains a %s for the filename to pass, default to pipe on stdin
189    * extradefs are of the form:
190    *  def1="definition"; def2="define \;";
191    * line wraps with a \ at the end of the line
192    * # for comments
193    */
194
195   /* find length of basetype */
196   if ((ch = strchr (type, '/')) == NULL)
197     return FALSE;
198   btlen = ch - type;
199
200   if ((fp = fopen (filename, "r")) != NULL)
201   {
202     while (!found && (buf = mutt_read_line (buf, &buflen, fp, &line)) != NULL)
203     {
204       /* ignore comments */
205       if (*buf == '#')
206         continue;
207       dprint (2, (debugfile, "mailcap entry: %s\n", buf));
208
209       /* check type */
210       ch = get_field (buf);
211       if (ascii_strcasecmp (buf, type) &&
212           (ascii_strncasecmp (buf, type, btlen) ||
213            (buf[btlen] != 0 &&                  /* implicit wild */
214             mutt_strcmp (buf + btlen, "/*"))))  /* wildsubtype */
215         continue;
216
217       /* next field is the viewcommand */
218       field = ch;
219       ch = get_field (ch);
220       if (entry)
221         entry->command = safe_strdup (field);
222
223       /* parse the optional fields */
224       found = TRUE;
225       copiousoutput = FALSE;
226       composecommand = FALSE;
227       editcommand = FALSE;
228       printcommand = FALSE;
229
230       while (ch)
231       {
232         field = ch;
233         ch = get_field (ch);
234         dprint (2, (debugfile, "field: %s\n", field));
235
236         if (!ascii_strcasecmp (field, "needsterminal"))
237         {
238           if (entry)
239             entry->needsterminal = TRUE;
240         }
241         else if (!ascii_strcasecmp (field, "copiousoutput"))
242         {
243           copiousoutput = TRUE;
244           if (entry)
245             entry->copiousoutput = TRUE;
246         }
247         else if (!ascii_strncasecmp (field, "composetyped", 12))
248         {
249           /* this compare most occur before compose to match correctly */
250           if (get_field_text (field + 12, entry ? &entry->composetypecommand : NULL,
251                               type, filename, line))
252             composecommand = TRUE;
253         }
254         else if (!ascii_strncasecmp (field, "compose", 7))
255         {
256           if (get_field_text (field + 7, entry ? &entry->composecommand : NULL,
257                               type, filename, line))
258             composecommand = TRUE;
259         }
260         else if (!ascii_strncasecmp (field, "print", 5))
261         {
262           if (get_field_text (field + 5, entry ? &entry->printcommand : NULL,
263                               type, filename, line))
264             printcommand = TRUE;
265         }
266         else if (!ascii_strncasecmp (field, "edit", 4))
267         {
268           if (get_field_text (field + 4, entry ? &entry->editcommand : NULL,
269                               type, filename, line))
270             editcommand = TRUE;
271         }
272         else if (!ascii_strncasecmp (field, "nametemplate", 12))
273         {
274           get_field_text (field + 12, entry ? &entry->nametemplate : NULL,
275                           type, filename, line);
276         }
277         else if (!ascii_strncasecmp (field, "x-convert", 9))
278         {
279           get_field_text (field + 9, entry ? &entry->convert : NULL,
280                           type, filename, line);
281         }
282         else if (!ascii_strncasecmp (field, "test", 4))
283         {
284           /* 
285            * This routine executes the given test command to determine
286            * if this is the right entry.
287            */
288           char *test_command = NULL;
289           size_t len;
290
291           if (get_field_text (field + 4, &test_command, type, filename, line)
292               && test_command)
293           {
294             len = mutt_strlen (test_command) + STRING;
295             safe_realloc (&test_command, len);
296             rfc1524_expand_command (a, a->filename, type, test_command, len);
297             if (mutt_system (test_command))
298             {
299               /* a non-zero exit code means test failed */
300               found = FALSE;
301             }
302             FREE (&test_command);
303           }
304         }
305       } /* while (ch) */
306
307       if (opt == M_AUTOVIEW)
308       {
309         if (!copiousoutput)
310           found = FALSE;
311       }
312       else if (opt == M_COMPOSE)
313       {
314         if (!composecommand)
315           found = FALSE;
316       }
317       else if (opt == M_EDIT)
318       {
319         if (!editcommand)
320           found = FALSE;
321       }
322       else if (opt == M_PRINT)
323       {
324         if (!printcommand)
325           found = FALSE;
326       }
327       
328       if (!found)
329       {
330         /* reset */
331         if (entry)
332         {
333           FREE (&entry->command);
334           FREE (&entry->composecommand);
335           FREE (&entry->composetypecommand);
336           FREE (&entry->editcommand);
337           FREE (&entry->printcommand);
338           FREE (&entry->nametemplate);
339           FREE (&entry->convert);
340           entry->needsterminal = 0;
341           entry->copiousoutput = 0;
342         }
343       }
344     } /* while (!found && (buf = mutt_read_line ())) */
345     fclose (fp);
346   } /* if ((fp = fopen ())) */
347   FREE (&buf);
348   return found;
349 }
350
351 rfc1524_entry *rfc1524_new_entry(void)
352 {
353   return (rfc1524_entry *)safe_calloc(1, sizeof(rfc1524_entry));
354 }
355
356 void rfc1524_free_entry(rfc1524_entry **entry)
357 {
358   rfc1524_entry *p = *entry;
359
360   FREE (&p->command);
361   FREE (&p->testcommand);
362   FREE (&p->composecommand);
363   FREE (&p->composetypecommand);
364   FREE (&p->editcommand);
365   FREE (&p->printcommand);
366   FREE (&p->nametemplate);
367   FREE (entry);
368 }
369
370 /*
371  * rfc1524_mailcap_lookup attempts to find the given type in the
372  * list of mailcap files.  On success, this returns the entry information
373  * in *entry, and returns 1.  On failure (not found), returns 0.
374  * If entry == NULL just return 1 if the given type is found.
375  */
376 int rfc1524_mailcap_lookup (BODY *a, char *type, rfc1524_entry *entry, int opt)
377 {
378   char path[_POSIX_PATH_MAX];
379   int x;
380   int found = FALSE;
381   char *curr = MailcapPath;
382
383   /* rfc1524 specifies that a path of mailcap files should be searched.
384    * joy.  They say 
385    * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc
386    * and overriden by the MAILCAPS environment variable, and, just to be nice, 
387    * we'll make it specifiable in .muttrc
388    */
389   if (!curr || !*curr)
390   {
391     mutt_error _("No mailcap path specified");
392     return 0;
393   }
394
395   mutt_check_lookup_list (a, type, SHORT_STRING);
396
397   while (!found && *curr)
398   {
399     x = 0;
400     while (*curr && *curr != ':' && x < sizeof (path) - 1)
401     {
402       path[x++] = *curr;
403       curr++;
404     }
405     if (*curr)
406       curr++;
407
408     if (!x)
409       continue;
410     
411     path[x] = '\0';
412     mutt_expand_path (path, sizeof (path));
413
414     dprint(2,(debugfile,"Checking mailcap file: %s\n",path));
415     found = rfc1524_mailcap_parse (a, path, type, entry, opt);
416   }
417
418   if (entry && !found)
419     mutt_error (_("mailcap entry for type %s not found"), type);
420
421   return found;
422 }
423
424
425 /* This routine will create a _temporary_ filename matching the
426  * name template given if this needs to be done.
427  * 
428  * Please note that only the last path element of the
429  * template and/or the old file name will be used for the
430  * comparison and the temporary file name.
431  * 
432  * Returns 0 if oldfile is fine as is.
433  * Returns 1 if newfile specified
434  */
435
436 static void strnfcpy(char *d, char *s, size_t siz, size_t len)
437 {
438   if(len > siz)
439     len = siz - 1;
440   strfcpy(d, s, len);
441 }
442
443 int rfc1524_expand_filename (char *nametemplate,
444                              char *oldfile, 
445                              char *newfile,
446                              size_t nflen)
447 {
448   int i, j, k, ps, r;
449   char *s;
450   short lmatch = 0, rmatch = 0; 
451   char left[_POSIX_PATH_MAX];
452   char right[_POSIX_PATH_MAX];
453   
454   newfile[0] = 0;
455
456   /* first, ignore leading path components.
457    */
458   
459   if (nametemplate && (s = strrchr (nametemplate, '/')))
460     nametemplate = s + 1;
461
462   if (oldfile && (s = strrchr (oldfile, '/')))
463     oldfile = s + 1;
464     
465   if (!nametemplate)
466   {
467     if (oldfile)
468       strfcpy (newfile, oldfile, nflen);
469   }
470   else if (!oldfile)
471   {
472     mutt_expand_fmt (newfile, nflen, nametemplate, "mutt");
473   }
474   else /* oldfile && nametemplate */
475   {
476
477     /* first, compare everything left from the "%s" 
478      * (if there is one).
479      */
480     
481     lmatch = 1; ps = 0;
482     for(i = 0; nametemplate[i]; i++)
483     {
484       if(nametemplate[i] == '%' && nametemplate[i+1] == 's')
485       { 
486         ps = 1;
487         break;
488       }
489
490       /* note that the following will _not_ read beyond oldfile's end. */
491
492       if(lmatch && nametemplate[i] != oldfile[i])
493         lmatch = 0;
494     }
495
496     if(ps)
497     {
498       
499       /* If we had a "%s", check the rest. */
500       
501       /* now, for the right part: compare everything right from 
502        * the "%s" to the final part of oldfile.
503        * 
504        * The logic here is as follows:
505        * 
506        * - We start reading from the end.
507        * - There must be a match _right_ from the "%s",
508        *   thus the i + 2.  
509        * - If there was a left hand match, this stuff
510        *   must not be counted again.  That's done by the
511        *   condition (j >= (lmatch ? i : 0)).
512        */
513       
514       rmatch = 1;
515
516       for(r = 0, j = mutt_strlen(oldfile) - 1, k = mutt_strlen(nametemplate) - 1 ;
517           j >= (lmatch ? i : 0) && k >= i + 2;
518           j--, k--)
519       {
520         if(nametemplate[k] != oldfile[j])
521         {
522           rmatch = 0;
523           break;
524         }
525       }
526       
527       /* Now, check if we had a full match. */
528       
529       if(k >= i + 2)
530         rmatch = 0;
531       
532       if(lmatch) *left = 0;
533       else strnfcpy(left, nametemplate, sizeof(left), i);
534       
535       if(rmatch) *right = 0;
536       else strfcpy(right, nametemplate + i + 2, sizeof(right));
537       
538       snprintf(newfile, nflen, "%s%s%s", left, oldfile, right);
539     }
540     else
541     {
542       /* no "%s" in the name template. */
543       strfcpy(newfile, nametemplate, nflen);
544     }
545   }
546   
547   mutt_adv_mktemp(newfile, nflen);
548
549   if(rmatch && lmatch)
550     return 0;
551   else 
552     return 1;
553   
554 }
555
556 /* If rfc1524_expand_command() is used on a recv'd message, then
557  * the filename doesn't exist yet, but if its used while sending a message,
558  * then we need to rename the existing file.
559  *
560  * This function returns 0 on successful move, 1 on old file doesn't exist,
561  * 2 on new file already exists, and 3 on other failure.
562  */
563
564 /* note on access(2) use: No dangling symlink problems here due to
565  * safe_fopen().
566  */
567
568 int _mutt_rename_file (char *oldfile, char *newfile, int overwrite)
569 {
570   FILE *ofp, *nfp;
571
572   if (access (oldfile, F_OK) != 0)
573     return 1;
574   if (!overwrite && access (newfile, F_OK) == 0)
575     return 2;
576   if ((ofp = fopen (oldfile,"r")) == NULL)
577     return 3;
578   if ((nfp = safe_fopen (newfile,"w")) == NULL)
579   {
580     fclose(ofp);
581     return 3;
582   }
583   mutt_copy_stream (ofp,nfp);
584   fclose (nfp);
585   fclose (ofp);
586   mutt_unlink (oldfile);
587   return 0;
588 }
589
590 int mutt_rename_file (char *oldfile, char *newfile)
591 {
592   return _mutt_rename_file (oldfile, newfile, 0);
593 }