1 /**
2 * @file
3 * Config/command parsing
4 *
5 * @authors
6 * Copyright (C) 1996-2002,2010,2013,2016 Michael R. Elkins <me@mutt.org>
7 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
8 *
9 * @copyright
10 * This program is free software: you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License as published by the Free Software
12 * Foundation, either version 2 of the License, or (at your option) any later
13 * version.
14 *
15 * This program is distributed in the hope that it will be useful, but WITHOUT
16 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 * details.
19 *
20 * You should have received a copy of the GNU General Public License along with
21 * this program. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /**
25 * @page neo_init Config/command parsing
26 *
27 * Config/command parsing
28 */
29
30 #include "config.h"
31 #include <ctype.h>
32 #include <pwd.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <sys/utsname.h>
39 #include <unistd.h>
40 #include "mutt/lib.h"
41 #include "address/lib.h"
42 #include "config/lib.h"
43 #include "email/lib.h"
44 #include "core/lib.h"
45 #include "alias/lib.h"
46 #include "conn/lib.h" // IWYU pragma: keep
47 #include "gui/lib.h"
48 #include "mutt.h"
49 #include "init.h"
50 #include "color/lib.h"
51 #include "history/lib.h"
52 #include "notmuch/lib.h"
53 #include "command_parse.h"
54 #include "context.h"
55 #include "functions.h"
56 #include "keymap.h"
57 #include "mutt_commands.h"
58 #include "mutt_globals.h"
59 #ifdef USE_LUA
60 #include "mutt_lua.h"
61 #endif
62 #include "menu/lib.h"
63 #include "muttlib.h"
64 #include "myvar.h"
65 #include "options.h"
66 #include "protos.h"
67 #include "sort.h"
68 #ifdef USE_SIDEBAR
69 #include "sidebar/lib.h"
70 #endif
71 #ifdef USE_COMP_MBOX
72 #include "compmbox/lib.h"
73 #endif
74 #ifdef USE_IMAP
75 #include "imap/lib.h"
76 #endif
77
78 /* Initial string that starts completion. No telling how much the user has
79 * typed so far. Allocate 1024 just to be sure! */
80 static char UserTyped[1024] = { 0 };
81
82 static int NumMatched = 0; /* Number of matches for completion */
83 static char Completed[256] = { 0 }; /* completed string (command or variable) */
84 static const char **Matches;
85 /* this is a lie until mutt_init runs: */
86 static int MatchesListsize = 512; // Enough space for all of the config items
87
88 #ifdef USE_NOTMUCH
89 /* List of tags found in last call to mutt_nm_query_complete(). */
90 static char **nm_tags;
91 #endif
92
93 /**
94 * matches_ensure_morespace - Allocate more space for auto-completion
95 * @param current Current allocation
96 */
matches_ensure_morespace(int current)97 static void matches_ensure_morespace(int current)
98 {
99 if (current <= (MatchesListsize - 2))
100 return;
101
102 int base_space = 512; // Enough space for all of the config items
103 int extra_space = MatchesListsize - base_space;
104 extra_space *= 2;
105 const int space = base_space + extra_space;
106 mutt_mem_realloc(&Matches, space * sizeof(char *));
107 memset(&Matches[current + 1], 0, space - current);
108 MatchesListsize = space;
109 }
110
111 /**
112 * candidate - Helper function for completion
113 * @param user User entered data for completion
114 * @param src Candidate for completion
115 * @param dest Completion result gets here
116 * @param dlen Length of dest buffer
117 *
118 * Changes the dest buffer if necessary/possible to aid completion.
119 */
candidate(char * user,const char * src,char * dest,size_t dlen)120 static void candidate(char *user, const char *src, char *dest, size_t dlen)
121 {
122 if (!dest || !user || !src)
123 return;
124
125 if (strstr(src, user) != src)
126 return;
127
128 matches_ensure_morespace(NumMatched);
129 Matches[NumMatched++] = src;
130 if (dest[0] == '\0')
131 mutt_str_copy(dest, src, dlen);
132 else
133 {
134 int l;
135 for (l = 0; src[l] && src[l] == dest[l]; l++)
136 ; // do nothing
137
138 dest[l] = '\0';
139 }
140 }
141
142 #ifdef USE_NOTMUCH
143 /**
144 * complete_all_nm_tags - Pass a list of Notmuch tags to the completion code
145 * @param pt List of all Notmuch tags
146 * @retval 0 Success
147 * @retval -1 Error
148 */
complete_all_nm_tags(const char * pt)149 static int complete_all_nm_tags(const char *pt)
150 {
151 struct Mailbox *m = ctx_mailbox(Context);
152 int tag_count_1 = 0;
153 int tag_count_2 = 0;
154
155 NumMatched = 0;
156 mutt_str_copy(UserTyped, pt, sizeof(UserTyped));
157 memset(Matches, 0, MatchesListsize);
158 memset(Completed, 0, sizeof(Completed));
159
160 nm_db_longrun_init(m, false);
161
162 /* Work out how many tags there are. */
163 if (nm_get_all_tags(m, NULL, &tag_count_1) || (tag_count_1 == 0))
164 goto done;
165
166 /* Free the old list, if any. */
167 if (nm_tags)
168 {
169 for (int i = 0; nm_tags[i]; i++)
170 FREE(&nm_tags[i]);
171 FREE(&nm_tags);
172 }
173 /* Allocate a new list, with sentinel. */
174 nm_tags = mutt_mem_malloc((tag_count_1 + 1) * sizeof(char *));
175 nm_tags[tag_count_1] = NULL;
176
177 /* Get all the tags. */
178 if (nm_get_all_tags(m, nm_tags, &tag_count_2) || (tag_count_1 != tag_count_2))
179 {
180 FREE(&nm_tags);
181 nm_tags = NULL;
182 nm_db_longrun_done(m);
183 return -1;
184 }
185
186 /* Put them into the completion machinery. */
187 for (int num = 0; num < tag_count_1; num++)
188 {
189 candidate(UserTyped, nm_tags[num], Completed, sizeof(Completed));
190 }
191
192 matches_ensure_morespace(NumMatched);
193 Matches[NumMatched++] = UserTyped;
194
195 done:
196 nm_db_longrun_done(m);
197 return 0;
198 }
199 #endif
200
201 /**
202 * execute_commands - Execute a set of NeoMutt commands
203 * @param p List of command strings
204 * @retval 0 Success, all the commands succeeded
205 * @retval -1 Error
206 */
execute_commands(struct ListHead * p)207 static int execute_commands(struct ListHead *p)
208 {
209 int rc = 0;
210 struct Buffer *err = mutt_buffer_pool_get();
211
212 struct ListNode *np = NULL;
213 STAILQ_FOREACH(np, p, entries)
214 {
215 enum CommandResult rc2 = mutt_parse_rc_line(np->data, err);
216 if (rc2 == MUTT_CMD_ERROR)
217 mutt_error(_("Error in command line: %s"), mutt_buffer_string(err));
218 else if (rc2 == MUTT_CMD_WARNING)
219 mutt_warning(_("Warning in command line: %s"), mutt_buffer_string(err));
220
221 if ((rc2 == MUTT_CMD_ERROR) || (rc2 == MUTT_CMD_WARNING))
222 {
223 mutt_buffer_pool_release(&err);
224 return -1;
225 }
226 }
227 mutt_buffer_pool_release(&err);
228
229 return rc;
230 }
231
232 /**
233 * find_cfg - Find a config file
234 * @param home User's home directory
235 * @param xdg_cfg_home XDG home directory
236 * @retval ptr Success, first matching directory
237 * @retval NULL Error, no matching directories
238 */
find_cfg(const char * home,const char * xdg_cfg_home)239 static char *find_cfg(const char *home, const char *xdg_cfg_home)
240 {
241 const char *names[] = {
242 "neomuttrc",
243 "muttrc",
244 NULL,
245 };
246
247 const char *locations[][2] = {
248 { xdg_cfg_home, "neomutt/" },
249 { xdg_cfg_home, "mutt/" },
250 { home, ".neomutt/" },
251 { home, ".mutt/" },
252 { home, "." },
253 { NULL, NULL },
254 };
255
256 for (int i = 0; locations[i][0] || locations[i][1]; i++)
257 {
258 if (!locations[i][0])
259 continue;
260
261 for (int j = 0; names[j]; j++)
262 {
263 char buf[256];
264
265 snprintf(buf, sizeof(buf), "%s/%s%s", locations[i][0], locations[i][1], names[j]);
266 if (access(buf, F_OK) == 0)
267 return mutt_str_dup(buf);
268 }
269 }
270
271 return NULL;
272 }
273
274 #ifndef DOMAIN
275 /**
276 * getmailname - Try to retrieve the FQDN from mailname files
277 * @retval ptr Heap allocated string with the FQDN
278 * @retval NULL No valid mailname file could be read
279 */
getmailname(void)280 static char *getmailname(void)
281 {
282 char *mailname = NULL;
283 static const char *mn_files[] = { "/etc/mailname", "/etc/mail/mailname" };
284
285 for (size_t i = 0; i < mutt_array_size(mn_files); i++)
286 {
287 FILE *fp = mutt_file_fopen(mn_files[i], "r");
288 if (!fp)
289 continue;
290
291 size_t len = 0;
292 mailname = mutt_file_read_line(NULL, &len, fp, NULL, MUTT_RL_NO_FLAGS);
293 mutt_file_fclose(&fp);
294 if (mailname && *mailname)
295 break;
296
297 FREE(&mailname);
298 }
299
300 return mailname;
301 }
302 #endif
303
304 /**
305 * get_hostname - Find the Fully-Qualified Domain Name
306 * @retval true Success
307 * @retval false Error, failed to find any name
308 *
309 * Use several methods to try to find the Fully-Qualified domain name of this host.
310 * If the user has already configured a hostname, this function will use it.
311 */
get_hostname(struct ConfigSet * cs)312 static bool get_hostname(struct ConfigSet *cs)
313 {
314 const char *short_host = NULL;
315 struct utsname utsname;
316
317 const char *const c_hostname = cs_subset_string(NeoMutt->sub, "hostname");
318 if (c_hostname)
319 {
320 short_host = c_hostname;
321 }
322 else
323 {
324 /* The call to uname() shouldn't fail, but if it does, the system is horribly
325 * broken, and the system's networking configuration is in an unreliable
326 * state. We should bail. */
327 if ((uname(&utsname)) == -1)
328 {
329 mutt_perror(_("unable to determine nodename via uname()"));
330 return false; // TEST09: can't test
331 }
332
333 short_host = utsname.nodename;
334 }
335
336 /* some systems report the FQDN instead of just the hostname */
337 char *dot = strchr(short_host, '.');
338 if (dot)
339 ShortHostname = mutt_strn_dup(short_host, dot - short_host);
340 else
341 ShortHostname = mutt_str_dup(short_host);
342
343 // All the code paths from here alloc memory for the fqdn
344 char *fqdn = mutt_str_dup(c_hostname);
345 if (!fqdn)
346 {
347 mutt_debug(LL_DEBUG1, "Setting $hostname\n");
348 /* now get FQDN. Use configured domain first, DNS next, then uname */
349 #ifdef DOMAIN
350 /* we have a compile-time domain name, use that for `$hostname` */
351 fqdn = mutt_mem_malloc(mutt_str_len(DOMAIN) + mutt_str_len(ShortHostname) + 2);
352 sprintf((char *) fqdn, "%s.%s", NONULL(ShortHostname), DOMAIN);
353 #else
354 fqdn = getmailname();
355 if (!fqdn)
356 {
357 struct Buffer *domain = mutt_buffer_pool_get();
358 if (getdnsdomainname(domain) == 0)
359 {
360 fqdn = mutt_mem_malloc(mutt_buffer_len(domain) + mutt_str_len(ShortHostname) + 2);
361 sprintf((char *) fqdn, "%s.%s", NONULL(ShortHostname), mutt_buffer_string(domain));
362 }
363 else
364 {
365 /* DNS failed, use the nodename. Whether or not the nodename had a '.'
366 * in it, we can use the nodename as the FQDN. On hosts where DNS is
367 * not being used, e.g. small network that relies on hosts files, a
368 * short host name is all that is required for SMTP to work correctly.
369 * It could be wrong, but we've done the best we can, at this point the
370 * onus is on the user to provide the correct hostname if the nodename
371 * won't work in their network. */
372 fqdn = mutt_str_dup(utsname.nodename);
373 }
374 mutt_buffer_pool_release(&domain);
375 mutt_debug(LL_DEBUG1, "Hostname: %s\n", NONULL(fqdn));
376 }
377 #endif
378 }
379
380 if (fqdn)
381 {
382 cs_str_initial_set(cs, "hostname", fqdn, NULL);
383 cs_str_reset(cs, "hostname", NULL);
384 FREE(&fqdn);
385 }
386
387 return true;
388 }
389
390 /**
391 * mutt_extract_token - Extract one token from a string
392 * @param dest Buffer for the result
393 * @param tok Buffer containing tokens
394 * @param flags Flags, see #TokenFlags
395 * @retval 0 Success
396 * @retval -1 Error
397 */
mutt_extract_token(struct Buffer * dest,struct Buffer * tok,TokenFlags flags)398 int mutt_extract_token(struct Buffer *dest, struct Buffer *tok, TokenFlags flags)
399 {
400 if (!dest || !tok)
401 return -1;
402
403 char ch;
404 char qc = '\0'; /* quote char */
405 char *pc = NULL;
406
407 /* Some callers used to rely on the (bad) assumption that dest->data would be
408 * non-NULL after calling this function. Perhaps I've missed a few cases, or
409 * a future caller might make the same mistake. */
410 if (!dest->data)
411 mutt_buffer_alloc(dest, 256);
412
413 mutt_buffer_reset(dest);
414
415 SKIPWS(tok->dptr);
416 while ((ch = *tok->dptr))
417 {
418 if (qc == '\0')
419 {
420 if ((IS_SPACE(ch) && !(flags & MUTT_TOKEN_SPACE)) ||
421 ((ch == '#') && !(flags & MUTT_TOKEN_COMMENT)) ||
422 ((ch == '+') && (flags & MUTT_TOKEN_PLUS)) ||
423 ((ch == '-') && (flags & MUTT_TOKEN_MINUS)) ||
424 ((ch == '=') && (flags & MUTT_TOKEN_EQUAL)) ||
425 ((ch == '?') && (flags & MUTT_TOKEN_QUESTION)) ||
426 ((ch == ';') && !(flags & MUTT_TOKEN_SEMICOLON)) ||
427 ((flags & MUTT_TOKEN_PATTERN) && strchr("~%=!|", ch)))
428 {
429 break;
430 }
431 }
432
433 tok->dptr++;
434
435 if (ch == qc)
436 qc = 0; /* end of quote */
437 else if (!qc && ((ch == '\'') || (ch == '"')) && !(flags & MUTT_TOKEN_QUOTE))
438 qc = ch;
439 else if ((ch == '\\') && (qc != '\''))
440 {
441 if (tok->dptr[0] == '\0')
442 return -1; /* premature end of token */
443 switch (ch = *tok->dptr++)
444 {
445 case 'c':
446 case 'C':
447 if (tok->dptr[0] == '\0')
448 return -1; /* premature end of token */
449 mutt_buffer_addch(dest, (toupper((unsigned char) tok->dptr[0]) - '@') & 0x7f);
450 tok->dptr++;
451 break;
452 case 'e':
453 mutt_buffer_addch(dest, '\033'); // Escape
454 break;
455 case 'f':
456 mutt_buffer_addch(dest, '\f');
457 break;
458 case 'n':
459 mutt_buffer_addch(dest, '\n');
460 break;
461 case 'r':
462 mutt_buffer_addch(dest, '\r');
463 break;
464 case 't':
465 mutt_buffer_addch(dest, '\t');
466 break;
467 default:
468 if (isdigit((unsigned char) ch) && isdigit((unsigned char) tok->dptr[0]) &&
469 isdigit((unsigned char) tok->dptr[1]))
470 {
471 mutt_buffer_addch(dest, (ch << 6) + (tok->dptr[0] << 3) + tok->dptr[1] - 3504);
472 tok->dptr += 2;
473 }
474 else
475 mutt_buffer_addch(dest, ch);
476 }
477 }
478 else if ((ch == '^') && (flags & MUTT_TOKEN_CONDENSE))
479 {
480 if (tok->dptr[0] == '\0')
481 return -1; /* premature end of token */
482 ch = *tok->dptr++;
483 if (ch == '^')
484 mutt_buffer_addch(dest, ch);
485 else if (ch == '[')
486 mutt_buffer_addch(dest, '\033'); // Escape
487 else if (isalpha((unsigned char) ch))
488 mutt_buffer_addch(dest, toupper((unsigned char) ch) - '@');
489 else
490 {
491 mutt_buffer_addch(dest, '^');
492 mutt_buffer_addch(dest, ch);
493 }
494 }
495 else if ((ch == '`') && (!qc || (qc == '"')))
496 {
497 FILE *fp = NULL;
498 pid_t pid;
499
500 pc = tok->dptr;
501 do
502 {
503 pc = strpbrk(pc, "\\`");
504 if (pc)
505 {
506 /* skip any quoted chars */
507 if (*pc == '\\')
508 pc += 2;
509 }
510 } while (pc && (pc[0] != '`'));
511 if (!pc)
512 {
513 mutt_debug(LL_DEBUG1, "mismatched backticks\n");
514 return -1;
515 }
516 struct Buffer cmd;
517 mutt_buffer_init(&cmd);
518 *pc = '\0';
519 if (flags & MUTT_TOKEN_BACKTICK_VARS)
520 {
521 /* recursively extract tokens to interpolate variables */
522 mutt_extract_token(&cmd, tok,
523 MUTT_TOKEN_QUOTE | MUTT_TOKEN_SPACE | MUTT_TOKEN_COMMENT |
524 MUTT_TOKEN_SEMICOLON | MUTT_TOKEN_NOSHELL);
525 }
526 else
527 {
528 cmd.data = mutt_str_dup(tok->dptr);
529 }
530 *pc = '`';
531 pid = filter_create(cmd.data, NULL, &fp, NULL);
532 if (pid < 0)
533 {
534 mutt_debug(LL_DEBUG1, "unable to fork command: %s\n", cmd.data);
535 FREE(&cmd.data);
536 return -1;
537 }
538
539 tok->dptr = pc + 1;
540
541 /* read line */
542 struct Buffer expn = mutt_buffer_make(0);
543 expn.data = mutt_file_read_line(NULL, &expn.dsize, fp, NULL, MUTT_RL_NO_FLAGS);
544 mutt_file_fclose(&fp);
545 int rc = filter_wait(pid);
546 if (rc != 0)
547 mutt_debug(LL_DEBUG1, "backticks exited code %d for command: %s\n", rc, cmd);
548 FREE(&cmd.data);
549
550 /* if we got output, make a new string consisting of the shell output
551 * plus whatever else was left on the original line */
552 /* BUT: If this is inside a quoted string, directly add output to
553 * the token */
554 if (expn.data)
555 {
556 if (qc)
557 {
558 mutt_buffer_addstr(dest, expn.data);
559 }
560 else
561 {
562 struct Buffer *copy = mutt_buffer_pool_get();
563 mutt_buffer_fix_dptr(&expn);
564 mutt_buffer_copy(copy, &expn);
565 mutt_buffer_addstr(copy, tok->dptr);
566 mutt_buffer_copy(tok, copy);
567 mutt_buffer_seek(tok, 0);
568 mutt_buffer_pool_release(©);
569 }
570 FREE(&expn.data);
571 }
572 }
573 else if ((ch == '$') && (!qc || (qc == '"')) &&
574 ((tok->dptr[0] == '{') || isalpha((unsigned char) tok->dptr[0])))
575 {
576 const char *env = NULL;
577 char *var = NULL;
578
579 if (tok->dptr[0] == '{')
580 {
581 pc = strchr(tok->dptr, '}');
582 if (pc)
583 {
584 var = mutt_strn_dup(tok->dptr + 1, pc - (tok->dptr + 1));
585 tok->dptr = pc + 1;
586
587 if ((flags & MUTT_TOKEN_NOSHELL))
588 {
589 mutt_buffer_addch(dest, ch);
590 mutt_buffer_addch(dest, '{');
591 mutt_buffer_addstr(dest, var);
592 mutt_buffer_addch(dest, '}');
593 FREE(&var);
594 }
595 }
596 }
597 else
598 {
599 for (pc = tok->dptr; isalnum((unsigned char) *pc) || (pc[0] == '_'); pc++)
600 ; // do nothing
601
602 var = mutt_strn_dup(tok->dptr, pc - tok->dptr);
603 tok->dptr = pc;
604 }
605 if (var)
606 {
607 struct Buffer result;
608 mutt_buffer_init(&result);
609 int rc = cs_subset_str_string_get(NeoMutt->sub, var, &result);
610
611 if (CSR_RESULT(rc) == CSR_SUCCESS)
612 {
613 mutt_buffer_addstr(dest, result.data);
614 FREE(&result.data);
615 }
616 else if ((env = myvar_get(var)))
617 {
618 mutt_buffer_addstr(dest, env);
619 }
620 else if (!(flags & MUTT_TOKEN_NOSHELL) && (env = mutt_str_getenv(var)))
621 {
622 mutt_buffer_addstr(dest, env);
623 }
624 else
625 {
626 mutt_buffer_addch(dest, ch);
627 mutt_buffer_addstr(dest, var);
628 }
629 FREE(&var);
630 }
631 }
632 else
633 mutt_buffer_addch(dest, ch);
634 }
635 mutt_buffer_addch(dest, 0); /* terminate the string */
636 SKIPWS(tok->dptr);
637 return 0;
638 }
639
640 /**
641 * mutt_opts_free - Clean up before quitting
642 */
mutt_opts_free(void)643 void mutt_opts_free(void)
644 {
645 clear_source_stack();
646
647 alias_shutdown();
648 #ifdef USE_SIDEBAR
649 sb_shutdown();
650 #endif
651
652 mutt_regexlist_free(&MailLists);
653 mutt_regexlist_free(&NoSpamList);
654 mutt_regexlist_free(&SubscribedLists);
655 mutt_regexlist_free(&UnMailLists);
656 mutt_regexlist_free(&UnSubscribedLists);
657
658 mutt_grouplist_free();
659 mutt_hash_free(&TagFormats);
660 mutt_hash_free(&TagTransforms);
661
662 /* Lists of strings */
663 mutt_list_free(&AlternativeOrderList);
664 mutt_list_free(&AutoViewList);
665 mutt_list_free(&HeaderOrderList);
666 mutt_list_free(&Ignore);
667 mutt_list_free(&MailToAllow);
668 mutt_list_free(&MimeLookupList);
669 mutt_list_free(&Muttrc);
670 mutt_list_free(&UnIgnore);
671 mutt_list_free(&UserHeader);
672
673 mutt_colors_cleanup();
674
675 FREE(&CurrentFolder);
676 FREE(&HomeDir);
677 FREE(&LastFolder);
678 FREE(&ShortHostname);
679 FREE(&Username);
680
681 mutt_replacelist_free(&SpamList);
682
683 mutt_delete_hooks(MUTT_HOOK_NO_FLAGS);
684
685 mutt_hist_free();
686 mutt_keys_free();
687
688 mutt_regexlist_free(&NoSpamList);
689 mutt_commands_free();
690 }
691
692 /**
693 * mutt_get_hook_type - Find a hook by name
694 * @param name Name to find
695 * @retval num Hook ID, e.g. #MUTT_FOLDER_HOOK
696 * @retval #MUTT_HOOK_NO_FLAGS Error, no matching hook
697 */
mutt_get_hook_type(const char * name)698 HookFlags mutt_get_hook_type(const char *name)
699 {
700 struct Command *c = NULL;
701 for (size_t i = 0, size = mutt_commands_array(&c); i < size; i++)
702 {
703 if (((c[i].parse == mutt_parse_hook) || (c[i].parse == mutt_parse_idxfmt_hook)) &&
704 mutt_istr_equal(c[i].name, name))
705 {
706 return c[i].data;
707 }
708 }
709 return MUTT_HOOK_NO_FLAGS;
710 }
711
712 /**
713 * mutt_init - Initialise NeoMutt
714 * @param cs Config Set
715 * @param skip_sys_rc If true, don't read the system config file
716 * @param commands List of config commands to execute
717 * @retval 0 Success
718 * @retval 1 Error
719 */
mutt_init(struct ConfigSet * cs,bool skip_sys_rc,struct ListHead * commands)720 int mutt_init(struct ConfigSet *cs, bool skip_sys_rc, struct ListHead *commands)
721 {
722 int need_pause = 0;
723 int rc = 1;
724 struct Buffer err = mutt_buffer_make(256);
725 struct Buffer buf = mutt_buffer_make(256);
726
727 mutt_grouplist_init();
728 alias_init();
729 mutt_commands_init();
730 #ifdef USE_COMP_MBOX
731 mutt_comp_init();
732 #endif
733 #ifdef USE_IMAP
734 imap_init();
735 #endif
736 #ifdef USE_LUA
737 mutt_lua_init();
738 #endif
739 TagTransforms = mutt_hash_new(64, MUTT_HASH_STRCASECMP);
740 TagFormats = mutt_hash_new(64, MUTT_HASH_NO_FLAGS);
741
742 menu_init();
743 #ifdef USE_SIDEBAR
744 sb_init();
745 #endif
746 #ifdef USE_NOTMUCH
747 nm_init();
748 #endif
749
750 /* "$spool_file" precedence: config file, environment */
751 const char *p = mutt_str_getenv("MAIL");
752 if (!p)
753 p = mutt_str_getenv("MAILDIR");
754 if (!p)
755 {
756 #ifdef HOMESPOOL
757 mutt_buffer_concat_path(&buf, NONULL(HomeDir), MAILPATH);
758 #else
759 mutt_buffer_concat_path(&buf, MAILPATH, NONULL(Username));
760 #endif
761 p = mutt_buffer_string(&buf);
762 }
763 cs_str_initial_set(cs, "spool_file", p, NULL);
764 cs_str_reset(cs, "spool_file", NULL);
765
766 p = mutt_str_getenv("REPLYTO");
767 if (p)
768 {
769 struct Buffer token;
770
771 mutt_buffer_printf(&buf, "Reply-To: %s", p);
772 mutt_buffer_init(&token);
773 parse_my_hdr(&token, &buf, 0, &err); /* adds to UserHeader */
774 FREE(&token.data);
775 }
776
777 p = mutt_str_getenv("EMAIL");
778 if (p)
779 {
780 cs_str_initial_set(cs, "from", p, NULL);
781 cs_str_reset(cs, "from", NULL);
782 }
783
784 /* "$mailcap_path" precedence: config file, environment, code */
785 const char *env_mc = mutt_str_getenv("MAILCAPS");
786 if (env_mc)
787 cs_str_string_set(cs, "mailcap_path", env_mc, NULL);
788
789 /* "$tmpdir" precedence: config file, environment, code */
790 const char *env_tmp = mutt_str_getenv("TMPDIR");
791 if (env_tmp)
792 cs_str_string_set(cs, "tmpdir", env_tmp, NULL);
793
794 /* "$visual", "$editor" precedence: config file, environment, code */
795 const char *env_ed = mutt_str_getenv("VISUAL");
796 if (!env_ed)
797 env_ed = mutt_str_getenv("EDITOR");
798 if (!env_ed)
799 env_ed = "vi";
800 cs_str_initial_set(cs, "editor", env_ed, NULL);
801
802 const char *const c_editor = cs_subset_string(NeoMutt->sub, "editor");
803 if (!c_editor)
804 cs_str_reset(cs, "editor", NULL);
805
806 const char *charset = mutt_ch_get_langinfo_charset();
807 cs_str_initial_set(cs, "charset", charset, NULL);
808 cs_str_reset(cs, "charset", NULL);
809 mutt_ch_set_charset(charset);
810 FREE(&charset);
811
812 Matches = mutt_mem_calloc(MatchesListsize, sizeof(char *));
813
814 #ifdef HAVE_GETSID
815 /* Unset suspend by default if we're the session leader */
816 if (getsid(0) == getpid())
817 cs_subset_str_native_set(NeoMutt->sub, "suspend", false, NULL);
818 #endif
819
820 /* RFC2368, "4. Unsafe headers"
821 * The creator of a mailto URL can't expect the resolver of a URL to
822 * understand more than the "subject" and "body" headers. Clients that
823 * resolve mailto URLs into mail messages should be able to correctly
824 * create RFC822-compliant mail messages using the "subject" and "body"
825 * headers. */
826 add_to_stailq(&MailToAllow, "body");
827 add_to_stailq(&MailToAllow, "subject");
828 /* Cc, In-Reply-To, and References help with not breaking threading on
829 * mailing lists, see https://github.com/neomutt/neomutt/issues/115 */
830 add_to_stailq(&MailToAllow, "cc");
831 add_to_stailq(&MailToAllow, "in-reply-to");
832 add_to_stailq(&MailToAllow, "references");
833
834 if (STAILQ_EMPTY(&Muttrc))
835 {
836 const char *xdg_cfg_home = mutt_str_getenv("XDG_CONFIG_HOME");
837
838 if (!xdg_cfg_home && HomeDir)
839 {
840 mutt_buffer_printf(&buf, "%s/.config", HomeDir);
841 xdg_cfg_home = mutt_buffer_string(&buf);
842 }
843
844 char *config = find_cfg(HomeDir, xdg_cfg_home);
845 if (config)
846 {
847 mutt_list_insert_tail(&Muttrc, config);
848 }
849 }
850 else
851 {
852 struct ListNode *np = NULL;
853 STAILQ_FOREACH(np, &Muttrc, entries)
854 {
855 mutt_buffer_strcpy(&buf, np->data);
856 FREE(&np->data);
857 mutt_buffer_expand_path(&buf);
858 np->data = mutt_buffer_strdup(&buf);
859 if (access(np->data, F_OK))
860 {
861 mutt_perror(np->data);
862 goto done; // TEST10: neomutt -F missing
863 }
864 }
865 }
866
867 if (!STAILQ_EMPTY(&Muttrc))
868 {
869 cs_str_string_set(cs, "alias_file", STAILQ_FIRST(&Muttrc)->data, NULL);
870 }
871
872 /* Process the global rc file if it exists and the user hasn't explicitly
873 * requested not to via "-n". */
874 if (!skip_sys_rc)
875 {
876 do
877 {
878 if (mutt_set_xdg_path(XDG_CONFIG_DIRS, &buf))
879 break;
880
881 mutt_buffer_printf(&buf, "%s/neomuttrc", SYSCONFDIR);
882 if (access(mutt_buffer_string(&buf), F_OK) == 0)
883 break;
884
885 mutt_buffer_printf(&buf, "%s/Muttrc", SYSCONFDIR);
886 if (access(mutt_buffer_string(&buf), F_OK) == 0)
887 break;
888
889 mutt_buffer_printf(&buf, "%s/neomuttrc", PKGDATADIR);
890 if (access(mutt_buffer_string(&buf), F_OK) == 0)
891 break;
892
893 mutt_buffer_printf(&buf, "%s/Muttrc", PKGDATADIR);
894 } while (false);
895
896 if (access(mutt_buffer_string(&buf), F_OK) == 0)
897 {
898 if (source_rc(mutt_buffer_string(&buf), &err) != 0)
899 {
900 mutt_error("%s", err.data);
901 need_pause = 1; // TEST11: neomutt (error in /etc/neomuttrc)
902 }
903 }
904 }
905
906 /* Read the user's initialization file. */
907 struct ListNode *np = NULL;
908 STAILQ_FOREACH(np, &Muttrc, entries)
909 {
910 if (np->data)
911 {
912 if (source_rc(np->data, &err) != 0)
913 {
914 mutt_error("%s", err.data);
915 need_pause = 1; // TEST12: neomutt (error in ~/.neomuttrc)
916 }
917 }
918 }
919
920 if (execute_commands(commands) != 0)
921 need_pause = 1; // TEST13: neomutt -e broken
922
923 if (!get_hostname(cs))
924 goto done;
925
926 {
927 char name[256] = { 0 };
928 const char *c_real_name = cs_subset_string(NeoMutt->sub, "real_name");
929 if (!c_real_name)
930 {
931 struct passwd *pw = getpwuid(getuid());
932 if (pw)
933 c_real_name = mutt_gecos_name(name, sizeof(name), pw);
934 }
935 cs_str_initial_set(cs, "real_name", c_real_name, NULL);
936 cs_str_reset(cs, "real_name", NULL);
937 }
938
939 if (need_pause && !OptNoCurses)
940 {
941 log_queue_flush(log_disp_terminal);
942 if (mutt_any_key_to_continue(NULL) == 'q')
943 goto done; // TEST14: neomutt -e broken (press 'q')
944 }
945
946 const char *const c_tmpdir = cs_subset_path(NeoMutt->sub, "tmpdir");
947 mutt_file_mkdir(c_tmpdir, S_IRWXU);
948
949 mutt_hist_init();
950 mutt_hist_read_file();
951
952 #ifdef USE_NOTMUCH
953 const bool c_virtual_spool_file =
954 cs_subset_bool(NeoMutt->sub, "virtual_spool_file");
955 if (c_virtual_spool_file)
956 {
957 /* Find the first virtual folder and open it */
958 struct MailboxList ml = STAILQ_HEAD_INITIALIZER(ml);
959 neomutt_mailboxlist_get_all(&ml, NeoMutt, MUTT_NOTMUCH);
960 struct MailboxNode *mp = STAILQ_FIRST(&ml);
961 if (mp)
962 cs_str_string_set(cs, "spool_file", mailbox_path(mp->mailbox), NULL);
963 neomutt_mailboxlist_clear(&ml);
964 }
965 #endif
966 rc = 0;
967
968 done:
969 mutt_buffer_dealloc(&err);
970 mutt_buffer_dealloc(&buf);
971 return rc;
972 }
973
974 /**
975 * mutt_parse_rc_buffer - Parse a line of user config
976 * @param line config line to read
977 * @param token scratch buffer to be used by parser
978 * @param err where to write error messages
979 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
980 *
981 * The reason for `token` is to avoid having to allocate and deallocate a lot
982 * of memory if we are parsing many lines. the caller can pass in the memory
983 * to use, which avoids having to create new space for every call to this function.
984 */
mutt_parse_rc_buffer(struct Buffer * line,struct Buffer * token,struct Buffer * err)985 enum CommandResult mutt_parse_rc_buffer(struct Buffer *line,
986 struct Buffer *token, struct Buffer *err)
987 {
988 if (mutt_buffer_len(line) == 0)
989 return 0;
990
991 enum CommandResult rc = MUTT_CMD_SUCCESS;
992
993 mutt_buffer_reset(err);
994
995 /* Read from the beginning of line->data */
996 mutt_buffer_seek(line, 0);
997
998 SKIPWS(line->dptr);
999 while (*line->dptr)
1000 {
1001 if (*line->dptr == '#')
1002 break; /* rest of line is a comment */
1003 if (*line->dptr == ';')
1004 {
1005 line->dptr++;
1006 continue;
1007 }
1008 mutt_extract_token(token, line, MUTT_TOKEN_NO_FLAGS);
1009
1010 struct Command *cmd = NULL;
1011 size_t size = mutt_commands_array(&cmd);
1012 size_t i;
1013 for (i = 0; i < size; i++)
1014 {
1015 if (mutt_str_equal(token->data, cmd[i].name))
1016 {
1017 mutt_debug(LL_NOTIFY, "NT_COMMAND: %s\n", cmd[i].name);
1018 rc = cmd[i].parse(token, line, cmd[i].data, err);
1019 if ((rc == MUTT_CMD_ERROR) || (rc == MUTT_CMD_FINISH))
1020 goto finish; /* Propagate return code */
1021
1022 notify_send(NeoMutt->notify, NT_COMMAND, i, (void *) cmd);
1023 break; /* Continue with next command */
1024 }
1025 }
1026 if (i == size)
1027 {
1028 mutt_buffer_printf(err, _("%s: unknown command"), NONULL(token->data));
1029 rc = MUTT_CMD_ERROR;
1030 break; /* Ignore the rest of the line */
1031 }
1032 }
1033 finish:
1034 return rc;
1035 }
1036
1037 /**
1038 * mutt_parse_rc_line - Parse a line of user config
1039 * @param line Config line to read
1040 * @param err Where to write error messages
1041 * @retval #CommandResult Result e.g. #MUTT_CMD_SUCCESS
1042 */
mutt_parse_rc_line(const char * line,struct Buffer * err)1043 enum CommandResult mutt_parse_rc_line(const char *line, struct Buffer *err)
1044 {
1045 if (!line || (*line == '\0'))
1046 return MUTT_CMD_ERROR;
1047
1048 struct Buffer *line_buffer = mutt_buffer_pool_get();
1049 struct Buffer *token = mutt_buffer_pool_get();
1050
1051 mutt_buffer_strcpy(line_buffer, line);
1052
1053 enum CommandResult rc = mutt_parse_rc_buffer(line_buffer, token, err);
1054
1055 mutt_buffer_pool_release(&line_buffer);
1056 mutt_buffer_pool_release(&token);
1057 return rc;
1058 }
1059
1060 /**
1061 * mutt_query_variables - Implement the -Q command line flag
1062 * @param queries List of query strings
1063 * @param show_docs If true, show one-liner docs for the config item
1064 * @retval 0 Success, all queries exist
1065 * @retval 1 Error
1066 */
mutt_query_variables(struct ListHead * queries,bool show_docs)1067 int mutt_query_variables(struct ListHead *queries, bool show_docs)
1068 {
1069 struct Buffer value = mutt_buffer_make(256);
1070 struct Buffer tmp = mutt_buffer_make(256);
1071 int rc = 0;
1072
1073 struct ListNode *np = NULL;
1074 STAILQ_FOREACH(np, queries, entries)
1075 {
1076 mutt_buffer_reset(&value);
1077
1078 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, np->data);
1079 if (!he)
1080 {
1081 mutt_warning(_("No such variable: %s"), np->data);
1082 rc = 1;
1083 continue;
1084 }
1085
1086 if (he->type & DT_DEPRECATED)
1087 {
1088 mutt_warning(_("Config variable '%s' is deprecated"), np->data);
1089 rc = 1;
1090 continue;
1091 }
1092
1093 int rv = cs_subset_he_string_get(NeoMutt->sub, he, &value);
1094 if (CSR_RESULT(rv) != CSR_SUCCESS)
1095 {
1096 rc = 1;
1097 continue;
1098 }
1099
1100 int type = DTYPE(he->type);
1101 if (type == DT_PATH)
1102 mutt_pretty_mailbox(value.data, value.dsize);
1103
1104 if ((type != DT_BOOL) && (type != DT_NUMBER) && (type != DT_LONG) && (type != DT_QUAD))
1105 {
1106 mutt_buffer_reset(&tmp);
1107 pretty_var(value.data, &tmp);
1108 mutt_buffer_strcpy(&value, tmp.data);
1109 }
1110
1111 dump_config_neo(NeoMutt->sub->cs, he, &value, NULL,
1112 show_docs ? CS_DUMP_SHOW_DOCS : CS_DUMP_NO_FLAGS, stdout);
1113 }
1114
1115 mutt_buffer_dealloc(&value);
1116 mutt_buffer_dealloc(&tmp);
1117
1118 return rc; // TEST16: neomutt -Q charset
1119 }
1120
1121 /**
1122 * mutt_command_complete - Complete a command name
1123 * @param buf Buffer for the result
1124 * @param buflen Length of the buffer
1125 * @param pos Cursor position in the buffer
1126 * @param numtabs Number of times the user has hit 'tab'
1127 * @retval 1 Success, a match
1128 * @retval 0 Error, no match
1129 */
mutt_command_complete(char * buf,size_t buflen,int pos,int numtabs)1130 int mutt_command_complete(char *buf, size_t buflen, int pos, int numtabs)
1131 {
1132 char *pt = buf;
1133 int spaces; /* keep track of the number of leading spaces on the line */
1134 struct MyVar *myv = NULL;
1135
1136 SKIPWS(buf);
1137 spaces = buf - pt;
1138
1139 pt = buf + pos - spaces;
1140 while ((pt > buf) && !isspace((unsigned char) *pt))
1141 pt--;
1142
1143 if (pt == buf) /* complete cmd */
1144 {
1145 /* first TAB. Collect all the matches */
1146 if (numtabs == 1)
1147 {
1148 NumMatched = 0;
1149 mutt_str_copy(UserTyped, pt, sizeof(UserTyped));
1150 memset(Matches, 0, MatchesListsize);
1151 memset(Completed, 0, sizeof(Completed));
1152
1153 struct Command *c = NULL;
1154 for (size_t num = 0, size = mutt_commands_array(&c); num < size; num++)
1155 candidate(UserTyped, c[num].name, Completed, sizeof(Completed));
1156 matches_ensure_morespace(NumMatched);
1157 Matches[NumMatched++] = UserTyped;
1158
1159 /* All matches are stored. Longest non-ambiguous string is ""
1160 * i.e. don't change 'buf'. Fake successful return this time */
1161 if (UserTyped[0] == '\0')
1162 return 1;
1163 }
1164
1165 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
1166 return 0;
1167
1168 /* NumMatched will _always_ be at least 1 since the initial
1169 * user-typed string is always stored */
1170 if ((numtabs == 1) && (NumMatched == 2))
1171 snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
1172 else if ((numtabs > 1) && (NumMatched > 2))
1173 {
1174 /* cycle through all the matches */
1175 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
1176 }
1177
1178 /* return the completed command */
1179 strncpy(buf, Completed, buflen - spaces);
1180 }
1181 else if (mutt_str_startswith(buf, "set") || mutt_str_startswith(buf, "unset") ||
1182 mutt_str_startswith(buf, "reset") || mutt_str_startswith(buf, "toggle"))
1183 { /* complete variables */
1184 static const char *const prefixes[] = { "no", "inv", "?", "&", 0 };
1185
1186 pt++;
1187 /* loop through all the possible prefixes (no, inv, ...) */
1188 if (mutt_str_startswith(buf, "set"))
1189 {
1190 for (int num = 0; prefixes[num]; num++)
1191 {
1192 if (mutt_str_startswith(pt, prefixes[num]))
1193 {
1194 pt += mutt_str_len(prefixes[num]);
1195 break;
1196 }
1197 }
1198 }
1199
1200 /* first TAB. Collect all the matches */
1201 if (numtabs == 1)
1202 {
1203 NumMatched = 0;
1204 mutt_str_copy(UserTyped, pt, sizeof(UserTyped));
1205 memset(Matches, 0, MatchesListsize);
1206 memset(Completed, 0, sizeof(Completed));
1207
1208 struct HashElem *he = NULL;
1209 struct HashElem **list = get_elem_list(NeoMutt->sub->cs);
1210 for (size_t i = 0; list[i]; i++)
1211 {
1212 he = list[i];
1213 const int type = DTYPE(he->type);
1214
1215 if ((type == DT_SYNONYM) || (type & DT_DEPRECATED))
1216 continue;
1217
1218 candidate(UserTyped, he->key.strkey, Completed, sizeof(Completed));
1219 }
1220 FREE(&list);
1221
1222 TAILQ_FOREACH(myv, &MyVars, entries)
1223 {
1224 candidate(UserTyped, myv->name, Completed, sizeof(Completed));
1225 }
1226 matches_ensure_morespace(NumMatched);
1227 Matches[NumMatched++] = UserTyped;
1228
1229 /* All matches are stored. Longest non-ambiguous string is ""
1230 * i.e. don't change 'buf'. Fake successful return this time */
1231 if (UserTyped[0] == '\0')
1232 return 1;
1233 }
1234
1235 if ((Completed[0] == 0) && UserTyped[0])
1236 return 0;
1237
1238 /* NumMatched will _always_ be at least 1 since the initial
1239 * user-typed string is always stored */
1240 if ((numtabs == 1) && (NumMatched == 2))
1241 snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
1242 else if ((numtabs > 1) && (NumMatched > 2))
1243 {
1244 /* cycle through all the matches */
1245 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
1246 }
1247
1248 strncpy(pt, Completed, buf + buflen - pt - spaces);
1249 }
1250 else if (mutt_str_startswith(buf, "exec"))
1251 {
1252 const enum MenuType mtype = menu_get_current_type();
1253 const struct Binding *menu = km_get_table(mtype);
1254 if (!menu && (mtype != MENU_PAGER))
1255 menu = OpGeneric;
1256
1257 pt++;
1258 /* first TAB. Collect all the matches */
1259 if (numtabs == 1)
1260 {
1261 NumMatched = 0;
1262 mutt_str_copy(UserTyped, pt, sizeof(UserTyped));
1263 memset(Matches, 0, MatchesListsize);
1264 memset(Completed, 0, sizeof(Completed));
1265 for (int num = 0; menu[num].name; num++)
1266 candidate(UserTyped, menu[num].name, Completed, sizeof(Completed));
1267 /* try the generic menu */
1268 if ((Completed[0] == '\0') && (mtype != MENU_PAGER))
1269 {
1270 menu = OpGeneric;
1271 for (int num = 0; menu[num].name; num++)
1272 candidate(UserTyped, menu[num].name, Completed, sizeof(Completed));
1273 }
1274 matches_ensure_morespace(NumMatched);
1275 Matches[NumMatched++] = UserTyped;
1276
1277 /* All matches are stored. Longest non-ambiguous string is ""
1278 * i.e. don't change 'buf'. Fake successful return this time */
1279 if (UserTyped[0] == '\0')
1280 return 1;
1281 }
1282
1283 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
1284 return 0;
1285
1286 /* NumMatched will _always_ be at least 1 since the initial
1287 * user-typed string is always stored */
1288 if ((numtabs == 1) && (NumMatched == 2))
1289 snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
1290 else if ((numtabs > 1) && (NumMatched > 2))
1291 {
1292 /* cycle through all the matches */
1293 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
1294 }
1295
1296 strncpy(pt, Completed, buf + buflen - pt - spaces);
1297 }
1298 else
1299 return 0;
1300
1301 return 1;
1302 }
1303
1304 /**
1305 * mutt_label_complete - Complete a label name
1306 * @param buf Buffer for the result
1307 * @param buflen Length of the buffer
1308 * @param numtabs Number of times the user has hit 'tab'
1309 * @retval 1 Success, a match
1310 * @retval 0 Error, no match
1311 */
mutt_label_complete(char * buf,size_t buflen,int numtabs)1312 int mutt_label_complete(char *buf, size_t buflen, int numtabs)
1313 {
1314 char *pt = buf;
1315 int spaces; /* keep track of the number of leading spaces on the line */
1316
1317 if (!Context || !Context->mailbox->label_hash)
1318 return 0;
1319
1320 SKIPWS(buf);
1321 spaces = buf - pt;
1322
1323 /* first TAB. Collect all the matches */
1324 if (numtabs == 1)
1325 {
1326 struct HashElem *entry = NULL;
1327 struct HashWalkState state = { 0 };
1328
1329 NumMatched = 0;
1330 mutt_str_copy(UserTyped, buf, sizeof(UserTyped));
1331 memset(Matches, 0, MatchesListsize);
1332 memset(Completed, 0, sizeof(Completed));
1333 while ((entry = mutt_hash_walk(Context->mailbox->label_hash, &state)))
1334 candidate(UserTyped, entry->key.strkey, Completed, sizeof(Completed));
1335 matches_ensure_morespace(NumMatched);
1336 qsort(Matches, NumMatched, sizeof(char *), (sort_t) mutt_istr_cmp);
1337 Matches[NumMatched++] = UserTyped;
1338
1339 /* All matches are stored. Longest non-ambiguous string is ""
1340 * i.e. don't change 'buf'. Fake successful return this time */
1341 if (UserTyped[0] == '\0')
1342 return 1;
1343 }
1344
1345 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
1346 return 0;
1347
1348 /* NumMatched will _always_ be at least 1 since the initial
1349 * user-typed string is always stored */
1350 if ((numtabs == 1) && (NumMatched == 2))
1351 snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
1352 else if ((numtabs > 1) && (NumMatched > 2))
1353 {
1354 /* cycle through all the matches */
1355 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
1356 }
1357
1358 /* return the completed label */
1359 strncpy(buf, Completed, buflen - spaces);
1360
1361 return 1;
1362 }
1363
1364 #ifdef USE_NOTMUCH
1365 /**
1366 * mutt_nm_query_complete - Complete to the nearest notmuch tag
1367 * @param buf Buffer for the result
1368 * @param buflen Length of the buffer
1369 * @param pos Cursor position in the buffer
1370 * @param numtabs Number of times the user has hit 'tab'
1371 * @retval true Success, a match
1372 * @retval false Error, no match
1373 *
1374 * Complete the nearest "tag:"-prefixed string previous to pos.
1375 */
mutt_nm_query_complete(char * buf,size_t buflen,int pos,int numtabs)1376 bool mutt_nm_query_complete(char *buf, size_t buflen, int pos, int numtabs)
1377 {
1378 char *pt = buf;
1379 int spaces;
1380
1381 SKIPWS(buf);
1382 spaces = buf - pt;
1383
1384 pt = (char *) mutt_strn_rfind((char *) buf, pos, "tag:");
1385 if (pt)
1386 {
1387 pt += 4;
1388 if (numtabs == 1)
1389 {
1390 /* First TAB. Collect all the matches */
1391 complete_all_nm_tags(pt);
1392
1393 /* All matches are stored. Longest non-ambiguous string is ""
1394 * i.e. don't change 'buf'. Fake successful return this time. */
1395 if (UserTyped[0] == '\0')
1396 return true;
1397 }
1398
1399 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
1400 return false;
1401
1402 /* NumMatched will _always_ be at least 1 since the initial
1403 * user-typed string is always stored */
1404 if ((numtabs == 1) && (NumMatched == 2))
1405 snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
1406 else if ((numtabs > 1) && (NumMatched > 2))
1407 {
1408 /* cycle through all the matches */
1409 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
1410 }
1411
1412 /* return the completed query */
1413 strncpy(pt, Completed, buf + buflen - pt - spaces);
1414 }
1415 else
1416 return false;
1417
1418 return true;
1419 }
1420 #endif
1421
1422 #ifdef USE_NOTMUCH
1423 /**
1424 * mutt_nm_tag_complete - Complete to the nearest notmuch tag
1425 * @param buf Buffer for the result
1426 * @param buflen Length of the buffer
1427 * @param numtabs Number of times the user has hit 'tab'
1428 * @retval true Success, a match
1429 * @retval false Error, no match
1430 *
1431 * Complete the nearest "+" or "-" -prefixed string previous to pos.
1432 */
mutt_nm_tag_complete(char * buf,size_t buflen,int numtabs)1433 bool mutt_nm_tag_complete(char *buf, size_t buflen, int numtabs)
1434 {
1435 if (!buf)
1436 return false;
1437
1438 char *pt = buf;
1439
1440 /* Only examine the last token */
1441 char *last_space = strrchr(buf, ' ');
1442 if (last_space)
1443 pt = (last_space + 1);
1444
1445 /* Skip the +/- */
1446 if ((pt[0] == '+') || (pt[0] == '-'))
1447 pt++;
1448
1449 if (numtabs == 1)
1450 {
1451 /* First TAB. Collect all the matches */
1452 complete_all_nm_tags(pt);
1453
1454 /* All matches are stored. Longest non-ambiguous string is ""
1455 * i.e. don't change 'buf'. Fake successful return this time. */
1456 if (UserTyped[0] == '\0')
1457 return true;
1458 }
1459
1460 if ((Completed[0] == '\0') && (UserTyped[0] != '\0'))
1461 return false;
1462
1463 /* NumMatched will _always_ be at least 1 since the initial
1464 * user-typed string is always stored */
1465 if ((numtabs == 1) && (NumMatched == 2))
1466 snprintf(Completed, sizeof(Completed), "%s", Matches[0]);
1467 else if ((numtabs > 1) && (NumMatched > 2))
1468 {
1469 /* cycle through all the matches */
1470 snprintf(Completed, sizeof(Completed), "%s", Matches[(numtabs - 2) % NumMatched]);
1471 }
1472
1473 /* return the completed query */
1474 strncpy(pt, Completed, buf + buflen - pt);
1475
1476 return true;
1477 }
1478 #endif
1479
1480 /**
1481 * mutt_var_value_complete - Complete a variable/value
1482 * @param buf Buffer for the result
1483 * @param buflen Length of the buffer
1484 * @param pos Cursor position in the buffer
1485 */
mutt_var_value_complete(char * buf,size_t buflen,int pos)1486 int mutt_var_value_complete(char *buf, size_t buflen, int pos)
1487 {
1488 char *pt = buf;
1489
1490 if (buf[0] == '\0')
1491 return 0;
1492
1493 SKIPWS(buf);
1494 const int spaces = buf - pt;
1495
1496 pt = buf + pos - spaces;
1497 while ((pt > buf) && !isspace((unsigned char) *pt))
1498 pt--;
1499 pt++; /* move past the space */
1500 if (*pt == '=') /* abort if no var before the '=' */
1501 return 0;
1502
1503 if (mutt_str_startswith(buf, "set"))
1504 {
1505 const char *myvarval = NULL;
1506 char var[256];
1507 mutt_str_copy(var, pt, sizeof(var));
1508 /* ignore the trailing '=' when comparing */
1509 int vlen = mutt_str_len(var);
1510 if (vlen == 0)
1511 return 0;
1512
1513 var[vlen - 1] = '\0';
1514
1515 struct HashElem *he = cs_subset_lookup(NeoMutt->sub, var);
1516 if (!he)
1517 {
1518 myvarval = myvar_get(var);
1519 if (myvarval)
1520 {
1521 struct Buffer pretty = mutt_buffer_make(256);
1522 pretty_var(myvarval, &pretty);
1523 snprintf(pt, buflen - (pt - buf), "%s=%s", var, pretty.data);
1524 mutt_buffer_dealloc(&pretty);
1525 return 1;
1526 }
1527 return 0; /* no such variable. */
1528 }
1529 else
1530 {
1531 struct Buffer value = mutt_buffer_make(256);
1532 struct Buffer pretty = mutt_buffer_make(256);
1533 int rc = cs_subset_he_string_get(NeoMutt->sub, he, &value);
1534 if (CSR_RESULT(rc) == CSR_SUCCESS)
1535 {
1536 pretty_var(value.data, &pretty);
1537 snprintf(pt, buflen - (pt - buf), "%s=%s", var, pretty.data);
1538 mutt_buffer_dealloc(&value);
1539 mutt_buffer_dealloc(&pretty);
1540 return 0;
1541 }
1542 mutt_buffer_dealloc(&value);
1543 mutt_buffer_dealloc(&pretty);
1544 return 1;
1545 }
1546 }
1547 return 0;
1548 }
1549