Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * RFC1524 Mailcap routines |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 1996-2000,2003,2012 Michael R. Elkins <me@mutt.org> |
7 | | * |
8 | | * @copyright |
9 | | * This program is free software: you can redistribute it and/or modify it under |
10 | | * the terms of the GNU General Public License as published by the Free Software |
11 | | * Foundation, either version 2 of the License, or (at your option) any later |
12 | | * version. |
13 | | * |
14 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
15 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
16 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
17 | | * details. |
18 | | * |
19 | | * You should have received a copy of the GNU General Public License along with |
20 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
21 | | */ |
22 | | |
23 | | /** |
24 | | * @page neo_mailcap RFC1524 Mailcap routines |
25 | | * |
26 | | * RFC1524 defines a format for the Multimedia Mail Configuration, which is the |
27 | | * standard mailcap file format under Unix which specifies what external |
28 | | * programs should be used to view/compose/edit multimedia files based on |
29 | | * content type. |
30 | | * |
31 | | * This file contains various functions for implementing a fair subset of |
32 | | * RFC1524. |
33 | | */ |
34 | | |
35 | | #include "config.h" |
36 | | #include <stdbool.h> |
37 | | #include <stdio.h> |
38 | | #include <string.h> |
39 | | #include "mutt/lib.h" |
40 | | #include "config/lib.h" |
41 | | #include "email/lib.h" |
42 | | #include "core/lib.h" |
43 | | #include "mailcap.h" |
44 | | #include "attach/lib.h" |
45 | | #include "muttlib.h" |
46 | | #include "protos.h" |
47 | | |
48 | | /** |
49 | | * mailcap_expand_command - Expand expandos in a command |
50 | | * @param a Email Body |
51 | | * @param filename File containing the email text |
52 | | * @param type Type, e.g. "text/plain" |
53 | | * @param command Buffer containing command |
54 | | * @retval 0 Command works on a file |
55 | | * @retval 1 Command works on a pipe |
56 | | * |
57 | | * The command semantics include the following: |
58 | | * %s is the filename that contains the mail body data |
59 | | * %t is the content type, like text/plain |
60 | | * %{parameter} is replaced by the parameter value from the content-type field |
61 | | * \% is % |
62 | | * Unsupported rfc1524 parameters: these would probably require some doing |
63 | | * by neomutt, and can probably just be done by piping the message to metamail |
64 | | * %n is the integer number of sub-parts in the multipart |
65 | | * %F is "content-type filename" repeated for each sub-part |
66 | | */ |
67 | | int mailcap_expand_command(struct Body *a, const char *filename, |
68 | | const char *type, struct Buffer *command) |
69 | 0 | { |
70 | 0 | int needspipe = true; |
71 | 0 | struct Buffer *buf = buf_pool_get(); |
72 | 0 | struct Buffer *quoted = buf_pool_get(); |
73 | 0 | struct Buffer *param = NULL; |
74 | 0 | struct Buffer *type2 = NULL; |
75 | |
|
76 | 0 | const bool c_mailcap_sanitize = cs_subset_bool(NeoMutt->sub, "mailcap_sanitize"); |
77 | 0 | const char *cptr = buf_string(command); |
78 | 0 | while (*cptr) |
79 | 0 | { |
80 | 0 | if (*cptr == '\\') |
81 | 0 | { |
82 | 0 | cptr++; |
83 | 0 | if (*cptr) |
84 | 0 | buf_addch(buf, *cptr++); |
85 | 0 | } |
86 | 0 | else if (*cptr == '%') |
87 | 0 | { |
88 | 0 | cptr++; |
89 | 0 | if (*cptr == '{') |
90 | 0 | { |
91 | 0 | const char *pvalue2 = NULL; |
92 | |
|
93 | 0 | if (param) |
94 | 0 | buf_reset(param); |
95 | 0 | else |
96 | 0 | param = buf_pool_get(); |
97 | | |
98 | | /* Copy parameter name into param buffer */ |
99 | 0 | cptr++; |
100 | 0 | while (*cptr && (*cptr != '}')) |
101 | 0 | buf_addch(param, *cptr++); |
102 | | |
103 | | /* In send mode, use the current charset, since the message hasn't |
104 | | * been converted yet. If noconv is set, then we assume the |
105 | | * charset parameter has the correct value instead. */ |
106 | 0 | if (mutt_istr_equal(buf_string(param), "charset") && a->charset && !a->noconv) |
107 | 0 | pvalue2 = a->charset; |
108 | 0 | else |
109 | 0 | pvalue2 = mutt_param_get(&a->parameter, buf_string(param)); |
110 | | |
111 | | /* Now copy the parameter value into param buffer */ |
112 | 0 | if (c_mailcap_sanitize) |
113 | 0 | buf_sanitize_filename(param, NONULL(pvalue2), false); |
114 | 0 | else |
115 | 0 | buf_strcpy(param, NONULL(pvalue2)); |
116 | |
|
117 | 0 | buf_quote_filename(quoted, buf_string(param), true); |
118 | 0 | buf_addstr(buf, buf_string(quoted)); |
119 | 0 | } |
120 | 0 | else if ((*cptr == 's') && filename) |
121 | 0 | { |
122 | 0 | buf_quote_filename(quoted, filename, true); |
123 | 0 | buf_addstr(buf, buf_string(quoted)); |
124 | 0 | needspipe = false; |
125 | 0 | } |
126 | 0 | else if (*cptr == 't') |
127 | 0 | { |
128 | 0 | if (!type2) |
129 | 0 | { |
130 | 0 | type2 = buf_pool_get(); |
131 | 0 | if (c_mailcap_sanitize) |
132 | 0 | buf_sanitize_filename(type2, type, false); |
133 | 0 | else |
134 | 0 | buf_strcpy(type2, type); |
135 | 0 | } |
136 | 0 | buf_quote_filename(quoted, buf_string(type2), true); |
137 | 0 | buf_addstr(buf, buf_string(quoted)); |
138 | 0 | } |
139 | |
|
140 | 0 | if (*cptr) |
141 | 0 | cptr++; |
142 | 0 | } |
143 | 0 | else |
144 | 0 | { |
145 | 0 | buf_addch(buf, *cptr++); |
146 | 0 | } |
147 | 0 | } |
148 | 0 | buf_copy(command, buf); |
149 | |
|
150 | 0 | buf_pool_release(&buf); |
151 | 0 | buf_pool_release("ed); |
152 | 0 | buf_pool_release(¶m); |
153 | 0 | buf_pool_release(&type2); |
154 | |
|
155 | 0 | return needspipe; |
156 | 0 | } |
157 | | |
158 | | /** |
159 | | * get_field - NUL terminate a RFC1524 field |
160 | | * @param s String to alter |
161 | | * @retval ptr Start of next field |
162 | | * @retval NULL Error |
163 | | */ |
164 | | static char *get_field(char *s) |
165 | 0 | { |
166 | 0 | if (!s) |
167 | 0 | return NULL; |
168 | | |
169 | 0 | char *ch = NULL; |
170 | |
|
171 | 0 | while ((ch = strpbrk(s, ";\\"))) |
172 | 0 | { |
173 | 0 | if (*ch == '\\') |
174 | 0 | { |
175 | 0 | s = ch + 1; |
176 | 0 | if (*s) |
177 | 0 | s++; |
178 | 0 | } |
179 | 0 | else |
180 | 0 | { |
181 | 0 | *ch = '\0'; |
182 | 0 | ch = mutt_str_skip_email_wsp(ch + 1); |
183 | 0 | break; |
184 | 0 | } |
185 | 0 | } |
186 | 0 | mutt_str_remove_trailing_ws(s); |
187 | 0 | return ch; |
188 | 0 | } |
189 | | |
190 | | /** |
191 | | * get_field_text - Get the matching text from a mailcap |
192 | | * @param field String to parse |
193 | | * @param entry Save the entry here |
194 | | * @param type Type, e.g. "text/plain" |
195 | | * @param filename Mailcap filename |
196 | | * @param line Mailcap line |
197 | | * @retval 1 Success |
198 | | * @retval 0 Failure |
199 | | */ |
200 | | static int get_field_text(char *field, char **entry, const char *type, |
201 | | const char *filename, int line) |
202 | 0 | { |
203 | 0 | field = mutt_str_skip_whitespace(field); |
204 | 0 | if (*field == '=') |
205 | 0 | { |
206 | 0 | if (entry) |
207 | 0 | { |
208 | 0 | field++; |
209 | 0 | field = mutt_str_skip_whitespace(field); |
210 | 0 | mutt_str_replace(entry, field); |
211 | 0 | } |
212 | 0 | return 1; |
213 | 0 | } |
214 | 0 | else |
215 | 0 | { |
216 | 0 | mutt_error(_("Improperly formatted entry for type %s in \"%s\" line %d"), |
217 | 0 | type, filename, line); |
218 | 0 | return 0; |
219 | 0 | } |
220 | 0 | } |
221 | | |
222 | | /** |
223 | | * rfc1524_mailcap_parse - Parse a mailcap entry |
224 | | * @param a Email Body |
225 | | * @param filename Filename |
226 | | * @param type Type, e.g. "text/plain" |
227 | | * @param entry Entry, e.g. "compose" |
228 | | * @param opt Option, see #MailcapLookup |
229 | | * @retval true Success |
230 | | * @retval false Failure |
231 | | */ |
232 | | static bool rfc1524_mailcap_parse(struct Body *a, const char *filename, const char *type, |
233 | | struct MailcapEntry *entry, enum MailcapLookup opt) |
234 | 0 | { |
235 | 0 | char *buf = NULL; |
236 | 0 | bool found = false; |
237 | 0 | int line = 0; |
238 | | |
239 | | /* rfc1524 mailcap file is of the format: |
240 | | * base/type; command; extradefs |
241 | | * type can be * for matching all |
242 | | * base with no /type is an implicit wild |
243 | | * command contains a %s for the filename to pass, default to pipe on stdin |
244 | | * extradefs are of the form: |
245 | | * def1="definition"; def2="define \;"; |
246 | | * line wraps with a \ at the end of the line |
247 | | * # for comments */ |
248 | | |
249 | | /* find length of basetype */ |
250 | 0 | char *ch = strchr(type, '/'); |
251 | 0 | if (!ch) |
252 | 0 | return false; |
253 | 0 | const int btlen = ch - type; |
254 | |
|
255 | 0 | FILE *fp = fopen(filename, "r"); |
256 | 0 | if (fp) |
257 | 0 | { |
258 | 0 | size_t buflen; |
259 | 0 | while (!found && (buf = mutt_file_read_line(buf, &buflen, fp, &line, MUTT_RL_CONT))) |
260 | 0 | { |
261 | | /* ignore comments */ |
262 | 0 | if (*buf == '#') |
263 | 0 | continue; |
264 | 0 | mutt_debug(LL_DEBUG2, "mailcap entry: %s\n", buf); |
265 | | |
266 | | /* check type */ |
267 | 0 | ch = get_field(buf); |
268 | 0 | if (!mutt_istr_equal(buf, type) && (!mutt_istrn_equal(buf, type, btlen) || |
269 | 0 | ((buf[btlen] != '\0') && /* implicit wild */ |
270 | 0 | !mutt_str_equal(buf + btlen, "/*")))) /* wildsubtype */ |
271 | 0 | { |
272 | 0 | continue; |
273 | 0 | } |
274 | | |
275 | | /* next field is the viewcommand */ |
276 | 0 | char *field = ch; |
277 | 0 | ch = get_field(ch); |
278 | 0 | if (entry) |
279 | 0 | entry->command = mutt_str_dup(field); |
280 | | |
281 | | /* parse the optional fields */ |
282 | 0 | found = true; |
283 | 0 | bool copiousoutput = false; |
284 | 0 | bool composecommand = false; |
285 | 0 | bool editcommand = false; |
286 | 0 | bool printcommand = false; |
287 | |
|
288 | 0 | while (ch) |
289 | 0 | { |
290 | 0 | field = ch; |
291 | 0 | ch = get_field(ch); |
292 | 0 | mutt_debug(LL_DEBUG2, "field: %s\n", field); |
293 | 0 | size_t plen; |
294 | |
|
295 | 0 | if (mutt_istr_equal(field, "needsterminal")) |
296 | 0 | { |
297 | 0 | if (entry) |
298 | 0 | entry->needsterminal = true; |
299 | 0 | } |
300 | 0 | else if (mutt_istr_equal(field, "copiousoutput")) |
301 | 0 | { |
302 | 0 | copiousoutput = true; |
303 | 0 | if (entry) |
304 | 0 | entry->copiousoutput = true; |
305 | 0 | } |
306 | 0 | else if ((plen = mutt_istr_startswith(field, "composetyped"))) |
307 | 0 | { |
308 | | /* this compare most occur before compose to match correctly */ |
309 | 0 | if (get_field_text(field + plen, entry ? &entry->composetypecommand : NULL, |
310 | 0 | type, filename, line)) |
311 | 0 | { |
312 | 0 | composecommand = true; |
313 | 0 | } |
314 | 0 | } |
315 | 0 | else if ((plen = mutt_istr_startswith(field, "compose"))) |
316 | 0 | { |
317 | 0 | if (get_field_text(field + plen, entry ? &entry->composecommand : NULL, |
318 | 0 | type, filename, line)) |
319 | 0 | { |
320 | 0 | composecommand = true; |
321 | 0 | } |
322 | 0 | } |
323 | 0 | else if ((plen = mutt_istr_startswith(field, "print"))) |
324 | 0 | { |
325 | 0 | if (get_field_text(field + plen, entry ? &entry->printcommand : NULL, |
326 | 0 | type, filename, line)) |
327 | 0 | { |
328 | 0 | printcommand = true; |
329 | 0 | } |
330 | 0 | } |
331 | 0 | else if ((plen = mutt_istr_startswith(field, "edit"))) |
332 | 0 | { |
333 | 0 | if (get_field_text(field + plen, entry ? &entry->editcommand : NULL, |
334 | 0 | type, filename, line)) |
335 | 0 | { |
336 | 0 | editcommand = true; |
337 | 0 | } |
338 | 0 | } |
339 | 0 | else if ((plen = mutt_istr_startswith(field, "nametemplate"))) |
340 | 0 | { |
341 | 0 | get_field_text(field + plen, entry ? &entry->nametemplate : NULL, |
342 | 0 | type, filename, line); |
343 | 0 | } |
344 | 0 | else if ((plen = mutt_istr_startswith(field, "x-convert"))) |
345 | 0 | { |
346 | 0 | get_field_text(field + plen, entry ? &entry->convert : NULL, type, filename, line); |
347 | 0 | } |
348 | 0 | else if ((plen = mutt_istr_startswith(field, "test"))) |
349 | 0 | { |
350 | | /* This routine executes the given test command to determine |
351 | | * if this is the right entry. */ |
352 | 0 | char *test_command = NULL; |
353 | |
|
354 | 0 | if (get_field_text(field + plen, &test_command, type, filename, line) && test_command) |
355 | 0 | { |
356 | 0 | struct Buffer *command = buf_pool_get(); |
357 | 0 | struct Buffer *afilename = buf_pool_get(); |
358 | 0 | buf_strcpy(command, test_command); |
359 | 0 | const bool c_mailcap_sanitize = cs_subset_bool(NeoMutt->sub, "mailcap_sanitize"); |
360 | 0 | if (c_mailcap_sanitize) |
361 | 0 | buf_sanitize_filename(afilename, NONULL(a->filename), true); |
362 | 0 | else |
363 | 0 | buf_strcpy(afilename, NONULL(a->filename)); |
364 | 0 | mailcap_expand_command(a, buf_string(afilename), type, command); |
365 | 0 | if (mutt_system(buf_string(command))) |
366 | 0 | { |
367 | | /* a non-zero exit code means test failed */ |
368 | 0 | found = false; |
369 | 0 | } |
370 | 0 | FREE(&test_command); |
371 | 0 | buf_pool_release(&command); |
372 | 0 | buf_pool_release(&afilename); |
373 | 0 | } |
374 | 0 | } |
375 | 0 | else if (mutt_istr_startswith(field, "x-neomutt-keep")) |
376 | 0 | { |
377 | 0 | if (entry) |
378 | 0 | entry->xneomuttkeep = true; |
379 | 0 | } |
380 | 0 | else if (mutt_istr_startswith(field, "x-neomutt-nowrap")) |
381 | 0 | { |
382 | 0 | if (entry) |
383 | 0 | entry->xneomuttnowrap = true; |
384 | 0 | a->nowrap = true; |
385 | 0 | } |
386 | 0 | } /* while (ch) */ |
387 | |
|
388 | 0 | if (opt == MUTT_MC_AUTOVIEW) |
389 | 0 | { |
390 | 0 | if (!copiousoutput) |
391 | 0 | found = false; |
392 | 0 | } |
393 | 0 | else if (opt == MUTT_MC_COMPOSE) |
394 | 0 | { |
395 | 0 | if (!composecommand) |
396 | 0 | found = false; |
397 | 0 | } |
398 | 0 | else if (opt == MUTT_MC_EDIT) |
399 | 0 | { |
400 | 0 | if (!editcommand) |
401 | 0 | found = false; |
402 | 0 | } |
403 | 0 | else if (opt == MUTT_MC_PRINT) |
404 | 0 | { |
405 | 0 | if (!printcommand) |
406 | 0 | found = false; |
407 | 0 | } |
408 | |
|
409 | 0 | if (!found) |
410 | 0 | { |
411 | | /* reset */ |
412 | 0 | if (entry) |
413 | 0 | { |
414 | 0 | FREE(&entry->command); |
415 | 0 | FREE(&entry->composecommand); |
416 | 0 | FREE(&entry->composetypecommand); |
417 | 0 | FREE(&entry->editcommand); |
418 | 0 | FREE(&entry->printcommand); |
419 | 0 | FREE(&entry->nametemplate); |
420 | 0 | FREE(&entry->convert); |
421 | 0 | entry->needsterminal = false; |
422 | 0 | entry->copiousoutput = false; |
423 | 0 | entry->xneomuttkeep = false; |
424 | 0 | } |
425 | 0 | } |
426 | 0 | } /* while (!found && (buf = mutt_file_read_line ())) */ |
427 | 0 | mutt_file_fclose(&fp); |
428 | 0 | } /* if ((fp = fopen ())) */ |
429 | 0 | FREE(&buf); |
430 | 0 | return found; |
431 | 0 | } |
432 | | |
433 | | /** |
434 | | * mailcap_entry_new - Allocate memory for a new rfc1524 entry |
435 | | * @retval ptr An un-initialized struct MailcapEntry |
436 | | */ |
437 | | struct MailcapEntry *mailcap_entry_new(void) |
438 | 0 | { |
439 | 0 | return mutt_mem_calloc(1, sizeof(struct MailcapEntry)); |
440 | 0 | } |
441 | | |
442 | | /** |
443 | | * mailcap_entry_free - Deallocate an struct MailcapEntry |
444 | | * @param[out] ptr MailcapEntry to deallocate |
445 | | */ |
446 | | void mailcap_entry_free(struct MailcapEntry **ptr) |
447 | 0 | { |
448 | 0 | if (!ptr || !*ptr) |
449 | 0 | return; |
450 | | |
451 | 0 | struct MailcapEntry *me = *ptr; |
452 | |
|
453 | 0 | FREE(&me->command); |
454 | 0 | FREE(&me->testcommand); |
455 | 0 | FREE(&me->composecommand); |
456 | 0 | FREE(&me->composetypecommand); |
457 | 0 | FREE(&me->editcommand); |
458 | 0 | FREE(&me->printcommand); |
459 | 0 | FREE(&me->nametemplate); |
460 | 0 | FREE(ptr); |
461 | 0 | } |
462 | | |
463 | | /** |
464 | | * mailcap_lookup - Find given type in the list of mailcap files |
465 | | * @param a Message body |
466 | | * @param type Text type in "type/subtype" format |
467 | | * @param typelen Length of the type |
468 | | * @param entry struct MailcapEntry to populate with results |
469 | | * @param opt Type of mailcap entry to lookup, see #MailcapLookup |
470 | | * @retval true If *entry is not NULL it populates it with the mailcap entry |
471 | | * @retval false No matching entry is found |
472 | | * |
473 | | * Find the given type in the list of mailcap files. |
474 | | */ |
475 | | bool mailcap_lookup(struct Body *a, char *type, size_t typelen, |
476 | | struct MailcapEntry *entry, enum MailcapLookup opt) |
477 | 0 | { |
478 | | /* rfc1524 specifies that a path of mailcap files should be searched. |
479 | | * joy. They say |
480 | | * $HOME/.mailcap:/etc/mailcap:/usr/etc/mailcap:/usr/local/etc/mailcap, etc |
481 | | * and overridden by the MAILCAPS environment variable, and, just to be nice, |
482 | | * we'll make it specifiable in .neomuttrc */ |
483 | 0 | const struct Slist *c_mailcap_path = cs_subset_slist(NeoMutt->sub, "mailcap_path"); |
484 | 0 | if (!c_mailcap_path || (c_mailcap_path->count == 0)) |
485 | 0 | { |
486 | | /* L10N: |
487 | | Mutt is trying to look up a mailcap value, but $mailcap_path is empty. |
488 | | We added a reference to the MAILCAPS environment variable as a hint too. |
489 | | |
490 | | Because the variable is automatically populated by Mutt, this |
491 | | should only occur if the user deliberately runs in their shell: |
492 | | export MAILCAPS= |
493 | | |
494 | | or deliberately runs inside Mutt or their .muttrc: |
495 | | set mailcap_path="" |
496 | | -or- |
497 | | unset mailcap_path |
498 | | */ |
499 | 0 | mutt_error(_("Neither mailcap_path nor MAILCAPS specified")); |
500 | 0 | return false; |
501 | 0 | } |
502 | | |
503 | 0 | mutt_check_lookup_list(a, type, typelen); |
504 | |
|
505 | 0 | struct Buffer *path = buf_pool_get(); |
506 | 0 | bool found = false; |
507 | |
|
508 | 0 | struct ListNode *np = NULL; |
509 | 0 | STAILQ_FOREACH(np, &c_mailcap_path->head, entries) |
510 | 0 | { |
511 | 0 | buf_strcpy(path, np->data); |
512 | 0 | buf_expand_path(path); |
513 | |
|
514 | 0 | mutt_debug(LL_DEBUG2, "Checking mailcap file: %s\n", buf_string(path)); |
515 | 0 | found = rfc1524_mailcap_parse(a, buf_string(path), type, entry, opt); |
516 | 0 | if (found) |
517 | 0 | break; |
518 | 0 | } |
519 | |
|
520 | 0 | buf_pool_release(&path); |
521 | |
|
522 | 0 | if (entry && !found) |
523 | 0 | mutt_error(_("mailcap entry for type %s not found"), type); |
524 | |
|
525 | 0 | return found; |
526 | 0 | } |
527 | | |
528 | | /** |
529 | | * mailcap_expand_filename - Expand a new filename from a template or existing filename |
530 | | * @param nametemplate Template |
531 | | * @param oldfile Original filename |
532 | | * @param newfile Buffer for new filename |
533 | | * |
534 | | * If there is no nametemplate, the stripped oldfile name is used as the |
535 | | * template for newfile. |
536 | | * |
537 | | * If there is no oldfile, the stripped nametemplate name is used as the |
538 | | * template for newfile. |
539 | | * |
540 | | * If both a nametemplate and oldfile are specified, the template is checked |
541 | | * for a "%s". If none is found, the nametemplate is used as the template for |
542 | | * newfile. The first path component of the nametemplate and oldfile are ignored. |
543 | | */ |
544 | | void mailcap_expand_filename(const char *nametemplate, const char *oldfile, |
545 | | struct Buffer *newfile) |
546 | 0 | { |
547 | 0 | int i, j, k; |
548 | 0 | char *s = NULL; |
549 | 0 | bool lmatch = false, rmatch = false; |
550 | |
|
551 | 0 | buf_reset(newfile); |
552 | | |
553 | | /* first, ignore leading path components */ |
554 | |
|
555 | 0 | if (nametemplate && (s = strrchr(nametemplate, '/'))) |
556 | 0 | nametemplate = s + 1; |
557 | |
|
558 | 0 | if (oldfile && (s = strrchr(oldfile, '/'))) |
559 | 0 | oldfile = s + 1; |
560 | |
|
561 | 0 | if (!nametemplate) |
562 | 0 | { |
563 | 0 | if (oldfile) |
564 | 0 | buf_strcpy(newfile, oldfile); |
565 | 0 | } |
566 | 0 | else if (!oldfile) |
567 | 0 | { |
568 | 0 | mutt_file_expand_fmt(newfile, nametemplate, "neomutt"); |
569 | 0 | } |
570 | 0 | else /* oldfile && nametemplate */ |
571 | 0 | { |
572 | | /* first, compare everything left from the "%s" |
573 | | * (if there is one). */ |
574 | |
|
575 | 0 | lmatch = true; |
576 | 0 | bool ps = false; |
577 | 0 | for (i = 0; nametemplate[i]; i++) |
578 | 0 | { |
579 | 0 | if ((nametemplate[i] == '%') && (nametemplate[i + 1] == 's')) |
580 | 0 | { |
581 | 0 | ps = true; |
582 | 0 | break; |
583 | 0 | } |
584 | | |
585 | | /* note that the following will _not_ read beyond oldfile's end. */ |
586 | | |
587 | 0 | if (lmatch && (nametemplate[i] != oldfile[i])) |
588 | 0 | lmatch = false; |
589 | 0 | } |
590 | |
|
591 | 0 | if (ps) |
592 | 0 | { |
593 | | /* If we had a "%s", check the rest. */ |
594 | | |
595 | | /* now, for the right part: compare everything right from |
596 | | * the "%s" to the final part of oldfile. |
597 | | * |
598 | | * The logic here is as follows: |
599 | | * |
600 | | * - We start reading from the end. |
601 | | * - There must be a match _right_ from the "%s", |
602 | | * thus the i + 2. |
603 | | * - If there was a left hand match, this stuff |
604 | | * must not be counted again. That's done by the |
605 | | * condition (j >= (lmatch ? i : 0)). */ |
606 | |
|
607 | 0 | rmatch = true; |
608 | |
|
609 | 0 | for (j = mutt_str_len(oldfile) - 1, k = mutt_str_len(nametemplate) - 1; |
610 | 0 | (j >= (lmatch ? i : 0)) && (k >= (i + 2)); j--, k--) |
611 | 0 | { |
612 | 0 | if (nametemplate[k] != oldfile[j]) |
613 | 0 | { |
614 | 0 | rmatch = false; |
615 | 0 | break; |
616 | 0 | } |
617 | 0 | } |
618 | | |
619 | | /* Now, check if we had a full match. */ |
620 | |
|
621 | 0 | if (k >= i + 2) |
622 | 0 | rmatch = false; |
623 | |
|
624 | 0 | struct Buffer *left = buf_pool_get(); |
625 | 0 | struct Buffer *right = buf_pool_get(); |
626 | |
|
627 | 0 | if (!lmatch) |
628 | 0 | buf_strcpy_n(left, nametemplate, i); |
629 | 0 | if (!rmatch) |
630 | 0 | buf_strcpy(right, nametemplate + i + 2); |
631 | 0 | buf_printf(newfile, "%s%s%s", buf_string(left), oldfile, buf_string(right)); |
632 | |
|
633 | 0 | buf_pool_release(&left); |
634 | 0 | buf_pool_release(&right); |
635 | 0 | } |
636 | 0 | else |
637 | 0 | { |
638 | | /* no "%s" in the name template. */ |
639 | 0 | buf_strcpy(newfile, nametemplate); |
640 | 0 | } |
641 | 0 | } |
642 | |
|
643 | 0 | mutt_adv_mktemp(newfile); |
644 | 0 | } |