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(&copy);
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