1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 2009-2021 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. */
16
17 #include "mail.h"
18 #include "mailutils/kwd.h"
19
20 #define MAILVAR_ALIAS 0x0001
21 #define MAILVAR_RDONLY 0x0002
22 #define MAILVAR_HIDDEN 0x0004
23
24 #define MAILVAR_TYPEMASK(type) (1<<(8+(type)))
25
26 enum mailvar_cmd
27 {
28 mailvar_cmd_set,
29 mailvar_cmd_unset
30 };
31
32 struct mailvar_symbol
33 {
34 struct mailvar_variable var;
35 int flags;
36 char *descr;
37 int (*handler) (enum mailvar_cmd, struct mailvar_variable *);
38 };
39
40 mu_list_t mailvar_list = NULL;
41
42 static int set_decode_fallback (enum mailvar_cmd cmd,
43 struct mailvar_variable *);
44 static int set_replyregex (enum mailvar_cmd cmd,
45 struct mailvar_variable *);
46 static int set_screen (enum mailvar_cmd cmd,
47 struct mailvar_variable *);
48 static int set_verbose (enum mailvar_cmd cmd,
49 struct mailvar_variable *);
50 static int set_debug (enum mailvar_cmd cmd,
51 struct mailvar_variable *);
52 static int set_folder (enum mailvar_cmd cmd,
53 struct mailvar_variable *);
54 static int set_headline (enum mailvar_cmd,
55 struct mailvar_variable *);
56 static int set_outfilename (enum mailvar_cmd,
57 struct mailvar_variable *);
58 static int set_escape (enum mailvar_cmd,
59 struct mailvar_variable *);
60
61 struct mailvar_symbol mailvar_tab[] =
62 {
63 /* FIXME: */
64 { { mailvar_name_allnet, }, MAILVAR_HIDDEN },
65
66 /* For compatibility with other mailx implementations.
67 Never used, always true. */
68 { { mailvar_name_append, },
69 MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_RDONLY,
70 /* TRANSLATORS: "mbox" is the name of a command. Don't translate it. */
71 N_("messages saved in mbox are appended to the end rather than prepended") },
72 { { mailvar_name_appenddeadletter, },
73 MAILVAR_TYPEMASK (mailvar_type_boolean),
74 /* TRANSLATORS: Don't translate "dead.letter". */
75 N_("append the contents of canceled letter to dead.letter file") },
76 { { mailvar_name_askbcc, },
77 MAILVAR_TYPEMASK (mailvar_type_boolean),
78 N_("prompt user for bcc before composing the message") },
79 { { mailvar_name_askcc, },
80 MAILVAR_TYPEMASK (mailvar_type_boolean),
81 N_("prompt user for cc before composing the message") },
82 { { mailvar_name_ask, },
83 MAILVAR_TYPEMASK (mailvar_type_boolean),
84 N_("prompt user for subject before composing the message") },
85 { { mailvar_name_asksub, }, MAILVAR_ALIAS, NULL },
86 { { mailvar_name_autoinc, },
87 MAILVAR_TYPEMASK (mailvar_type_boolean),
88 N_("automatically incorporate newly arrived messages")},
89 { { mailvar_name_autoprint, },
90 MAILVAR_TYPEMASK (mailvar_type_boolean),
91 /* TRANSLATORS: "delete" and "dp" are command names. */
92 N_("delete command behaves like dp") },
93 { { mailvar_name_bang, },
94 MAILVAR_TYPEMASK (mailvar_type_boolean),
95 N_("replace every occurrence of ! in arguments to the shell command"
96 " with the last executed command") },
97 { { mailvar_name_charset, },
98 MAILVAR_TYPEMASK (mailvar_type_string),
99 N_("output character set for decoded header fields") },
100 { { mailvar_name_cmd, },
101 MAILVAR_TYPEMASK (mailvar_type_string),
102 /* TRANSLATORS: "pipe" is the command name. */
103 N_("default shell command for pipe") },
104 { { mailvar_name_columns, },
105 MAILVAR_TYPEMASK (mailvar_type_number),
106 N_("number of columns on terminal screen") },
107 { { mailvar_name_crt, },
108 MAILVAR_TYPEMASK (mailvar_type_number) |
109 MAILVAR_TYPEMASK (mailvar_type_boolean),
110 N_("if numeric, sets the minimum number of output lines needed "
111 "to engage paging; if boolean, use the height of the terminal "
112 "screen to compute the threshold") },
113 { { mailvar_name_datefield, },
114 MAILVAR_TYPEMASK (mailvar_type_boolean),
115 N_("get date from the `Date:' header, instead of the envelope") },
116 { { mailvar_name_debug, },
117 MAILVAR_TYPEMASK (mailvar_type_string) |
118 MAILVAR_TYPEMASK (mailvar_type_boolean),
119 N_("set Mailutils debug level"),
120 set_debug },
121 { { mailvar_name_decode_fallback, },
122 MAILVAR_TYPEMASK (mailvar_type_string),
123 N_("how to represent characters that cannot be rendered using the "
124 "current character set"),
125 set_decode_fallback },
126 { { mailvar_name_dot, },
127 MAILVAR_TYPEMASK (mailvar_type_boolean),
128 N_("input message is terminated with a dot alone on a line") },
129 { { mailvar_name_editheaders, },
130 MAILVAR_TYPEMASK (mailvar_type_boolean),
131 N_("allow editing message headers while composing") },
132 { { mailvar_name_emptystart, },
133 MAILVAR_TYPEMASK (mailvar_type_boolean),
134 N_("start interactive mode if the mailbox is empty") },
135 { { mailvar_name_escape, },
136 MAILVAR_TYPEMASK (mailvar_type_string),
137 N_("command escape character"),
138 set_escape },
139 { { mailvar_name_flipr, },
140 MAILVAR_TYPEMASK (mailvar_type_boolean),
141 N_("swap the meaning of reply and Reply commands") },
142 { { mailvar_name_folder, },
143 MAILVAR_TYPEMASK (mailvar_type_string),
144 N_("folder directory name"),
145 set_folder },
146 { { mailvar_name_fromfield, },
147 MAILVAR_TYPEMASK (mailvar_type_boolean),
148 N_("get sender address from the `From:' header, instead of "
149 "the envelope") },
150 { { mailvar_name_gnu_last_command, },
151 MAILVAR_TYPEMASK (mailvar_type_string) | MAILVAR_RDONLY,
152 N_("last executed command line") },
153 { { mailvar_name_header, },
154 MAILVAR_TYPEMASK (mailvar_type_boolean),
155 N_("run the `headers' command after entering interactive mode") },
156 { { mailvar_name_headline, },
157 MAILVAR_TYPEMASK (mailvar_type_string),
158 N_("format string to use for the header summary"),
159 set_headline },
160 { { mailvar_name_hold, },
161 MAILVAR_TYPEMASK (mailvar_type_boolean),
162 N_("hold the read or saved messages in the system mailbox") },
163 { { mailvar_name_ignore, },
164 MAILVAR_TYPEMASK (mailvar_type_boolean),
165 N_("ignore keyboard interrupts when composing messages") },
166 { { mailvar_name_ignoreeof, },
167 MAILVAR_TYPEMASK (mailvar_type_boolean),
168 N_("ignore EOF character") },
169 { { mailvar_name_indentprefix, },
170 MAILVAR_TYPEMASK (mailvar_type_string),
171 N_("string used by the ~m escape for indenting quoted messages") },
172 { { mailvar_name_inplacealiases, },
173 MAILVAR_TYPEMASK (mailvar_type_boolean),
174 N_("expand aliases in the address header field "
175 "before starting composing the message") },
176 /* For compatibility with other mailx implementations.
177 Never used, always true. */
178 { { mailvar_name_keep, },
179 MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_RDONLY,
180 N_("keep the empty user's system mailbox,"
181 " instead of removing it") },
182 { { mailvar_name_keepsave, },
183 MAILVAR_TYPEMASK (mailvar_type_boolean),
184 N_("keep saved messages in system mailbox too") },
185 { { mailvar_name_mailx, },
186 MAILVAR_TYPEMASK (mailvar_type_boolean),
187 N_("enable mailx compatibility mode") },
188 { { mailvar_name_metamail, },
189 MAILVAR_TYPEMASK (mailvar_type_boolean),
190 N_("interpret the content of message parts; if set to a string "
191 "specifies the name of the external metamail command") },
192 { { mailvar_name_metoo, },
193 MAILVAR_TYPEMASK (mailvar_type_boolean),
194 N_("do not remove sender addresses from the recipient list") },
195 { { mailvar_name_mimenoask, },
196 MAILVAR_TYPEMASK (mailvar_type_string),
197 N_("a comma-separated list of MIME types for which "
198 "no confirmation is needed before running metamail interpreter") },
199 { { mailvar_name_mode, },
200 MAILVAR_TYPEMASK (mailvar_type_string) | MAILVAR_RDONLY,
201 N_("the name of current operation mode") },
202 { { mailvar_name_nullbody, },
203 MAILVAR_TYPEMASK (mailvar_type_boolean),
204 N_("accept messages with an empty body") },
205 { { mailvar_name_nullbodymsg, },
206 MAILVAR_TYPEMASK (mailvar_type_string),
207 N_("display this text when sending a message with empty body") },
208 { { mailvar_name_outfolder, },
209 MAILVAR_TYPEMASK (mailvar_type_string) |
210 MAILVAR_TYPEMASK (mailvar_type_boolean),
211 N_("If boolean, causes the files used to record outgoing messages to"
212 " be located in the directory specified by the folder variable"
213 " (unless the pathname is absolute).\n"
214 "If string, names the directory where to store these files.") },
215 { { mailvar_name_page, },
216 MAILVAR_TYPEMASK (mailvar_type_boolean),
217 N_("pipe command terminates each message with a formfeed") },
218 { { mailvar_name_prompt, },
219 MAILVAR_TYPEMASK (mailvar_type_string),
220 N_("command prompt sequence") },
221 { { mailvar_name_quit, },
222 MAILVAR_TYPEMASK (mailvar_type_boolean),
223 N_("keyboard interrupts terminate the program") },
224 { { mailvar_name_rc, },
225 MAILVAR_TYPEMASK (mailvar_type_boolean),
226 N_("read the system-wide configuration file upon startup") },
227 { { mailvar_name_readonly, },
228 MAILVAR_TYPEMASK (mailvar_type_boolean),
229 N_("mailboxes are opened in readonly mode") },
230 { { mailvar_name_record, },
231 MAILVAR_TYPEMASK (mailvar_type_string),
232 N_("save outgoing messages in this file") },
233 { { mailvar_name_recursivealiases, },
234 MAILVAR_TYPEMASK (mailvar_type_boolean),
235 N_("recursively expand aliases") },
236 { { mailvar_name_regex, },
237 MAILVAR_TYPEMASK (mailvar_type_boolean),
238 N_("use of regular expressions in search message specifications") },
239 { { mailvar_name_replyprefix, },
240 MAILVAR_TYPEMASK (mailvar_type_string),
241 N_("prefix for the subject line of a reply message") },
242 { { mailvar_name_replyregex, },
243 MAILVAR_TYPEMASK (mailvar_type_string),
244 N_("regexp for recognizing subject lines of reply messages"),
245 set_replyregex },
246 { { mailvar_name_return_address },
247 MAILVAR_TYPEMASK (mailvar_type_string),
248 N_("return address for outgoing messages") },
249 { { mailvar_name_save, },
250 MAILVAR_TYPEMASK (mailvar_type_boolean),
251 N_("store aborted messages in the user's dead.file") },
252 { { mailvar_name_screen, },
253 MAILVAR_TYPEMASK (mailvar_type_number),
254 N_("number of lines on terminal screen"),
255 set_screen },
256 { { mailvar_name_sendmail, },
257 MAILVAR_TYPEMASK (mailvar_type_string),
258 N_("URL of the mail transport agent") },
259 /* FIXME: Not yet used. */
260 { { mailvar_name_sendwait, },
261 MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_HIDDEN, NULL },
262 { { mailvar_name_sign, },
263 MAILVAR_TYPEMASK (mailvar_type_string),
264 N_("signature for use with the ~a command") },
265 { { mailvar_name_Sign, },
266 MAILVAR_TYPEMASK (mailvar_type_string),
267 N_("name of the signature file for use with the ~A command") },
268 { { mailvar_name_showenvelope, },
269 MAILVAR_TYPEMASK (mailvar_type_boolean),
270 N_("`print' command includes the SMTP envelope in its output") },
271 { { mailvar_name_showto, },
272 MAILVAR_TYPEMASK (mailvar_type_boolean),
273 N_("if the message was sent by me, print its recipient address "
274 "in the header summary") },
275 { { mailvar_name_toplines, },
276 MAILVAR_TYPEMASK (mailvar_type_number),
277 N_("number of lines to be displayed by `top' or `Top'") },
278 { { mailvar_name_variable_pretty_print, },
279 MAILVAR_TYPEMASK (mailvar_type_boolean),
280 N_("print variables with short descriptions") },
281 { { mailvar_name_varpp, }, MAILVAR_ALIAS },
282 { { mailvar_name_variable_strict, },
283 MAILVAR_TYPEMASK (mailvar_type_boolean),
284 N_("perform strict checking when setting variables") },
285 { { mailvar_name_varstrict, }, MAILVAR_ALIAS },
286 { { mailvar_name_verbose, },
287 MAILVAR_TYPEMASK (mailvar_type_boolean),
288 N_("verbosely trace the process of message delivery"),
289 set_verbose },
290
291 { { mailvar_name_useragent, },
292 MAILVAR_TYPEMASK (mailvar_type_boolean),
293 N_("add the `User-Agent' header to the outgoing messages") },
294 { { mailvar_name_xmailer, }, MAILVAR_ALIAS },
295
296 { { mailvar_name_mime },
297 MAILVAR_TYPEMASK (mailvar_type_boolean),
298 N_("always compose MIME messages") },
299
300 { { mailvar_name_fullnames },
301 MAILVAR_TYPEMASK (mailvar_type_boolean),
302 N_("when replying, preserve personal parts of recipient emails") },
303
304 { { mailvar_name_outfilename },
305 MAILVAR_TYPEMASK (mailvar_type_string),
306 N_("how to create outgoing file name: local, domain, email"),
307 set_outfilename },
308
309 { { mailvar_name_PID },
310 MAILVAR_TYPEMASK (mailvar_type_string) | MAILVAR_RDONLY,
311 N_("PID of this process") },
312
313 /* These will be implemented later */
314 { { mailvar_name_onehop, }, MAILVAR_HIDDEN, NULL },
315
316 { { mailvar_name_quiet, },
317 MAILVAR_TYPEMASK (mailvar_type_boolean) | MAILVAR_HIDDEN,
318 N_("suppress the printing of the version when first invoked") },
319
320 { { NULL }, }
321 };
322
323 static int mailvar_symbol_count =
324 sizeof (mailvar_tab) / sizeof (mailvar_tab[0]) - 1;
325
326 struct mailvar_symbol *
find_mailvar_symbol(const char * var)327 find_mailvar_symbol (const char *var)
328 {
329 struct mailvar_symbol *ep;
330 for (ep = mailvar_tab; ep->var.name; ep++)
331 if (strcmp (ep->var.name, var) == 0)
332 {
333 while ((ep->flags & MAILVAR_ALIAS) && ep > mailvar_tab)
334 ep--;
335 return ep;
336 }
337 return NULL;
338 }
339
340 static void
print_descr(mu_stream_t out,const char * s,int n,int doc_col,int rmargin,char * pfx)341 print_descr (mu_stream_t out, const char *s, int n,
342 int doc_col, int rmargin, char *pfx)
343 {
344 mu_stream_stat_buffer stat;
345
346 if (!s)
347 return;
348
349 mu_stream_set_stat (out, MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT), stat);
350 stat[MU_STREAM_STAT_OUT] = n;
351 do
352 {
353 const char *p;
354 const char *space = NULL;
355
356 if (stat[MU_STREAM_STAT_OUT] && pfx)
357 mu_stream_printf (out, "%s", pfx);
358
359 while (stat[MU_STREAM_STAT_OUT] < doc_col)
360 mu_stream_write (out, " ", 1, NULL);
361
362 for (p = s; *p && p < s + (rmargin - doc_col); p++)
363 if (*p == '\n')
364 {
365 space = p;
366 break;
367 }
368 else if (mu_isspace (*p))
369 space = p;
370
371 if (!space || (*space != '\n' && p < s + (rmargin - doc_col)))
372 {
373 mu_stream_printf (out, "%s", s);
374 s += strlen (s);
375 }
376 else
377 {
378 for (; s < space; s++)
379 mu_stream_write (out, s, 1, NULL);
380 for (; *s && mu_isspace (*s); s++)
381 ;
382 }
383 mu_stream_printf (out, "\n");
384 stat[MU_STREAM_STAT_OUT] = 1;
385 }
386 while (*s);
387 mu_stream_set_stat (out, 0, NULL);
388 }
389
390 /* Functions for dealing with internal mailvar_list variables */
391 static int
mailvar_variable_comp(const void * a,const void * b)392 mailvar_variable_comp (const void *a, const void *b)
393 {
394 const struct mailvar_variable *v1 = a;
395 const struct mailvar_variable *v2 = b;
396
397 return strcmp (v1->name, v2->name);
398 }
399
400 /* Find mailvar_list entry VAR. If not found and CREATE is not NULL, then
401 create the (unset and untyped) variable */
402 struct mailvar_variable *
mailvar_find_variable(const char * name,int create)403 mailvar_find_variable (const char *name, int create)
404 {
405 struct mailvar_symbol *sym;
406 struct mailvar_variable *var;
407
408 sym = find_mailvar_symbol (name);
409 if (sym)
410 var = &sym->var;
411 else
412 {
413 struct mailvar_variable entry, *p;
414
415 entry.name = (char*)name;
416 /* Store it into the list */
417 if (mailvar_list == NULL)
418 {
419 mu_list_create (&mailvar_list);
420 mu_list_set_comparator (mailvar_list, mailvar_variable_comp);
421 }
422
423 if (mu_list_locate (mailvar_list, &entry, (void**)&p))
424 {
425 if (!create)
426 return 0;
427 else
428 {
429 p = mu_alloc (sizeof *p);
430 p->name = mu_strdup (name);
431 mu_list_prepend (mailvar_list, p);
432 }
433 }
434 var = p;
435 var->set = 0;
436 var->type = mailvar_type_whatever;
437 var->value.number = 0;
438 }
439
440 return var;
441 }
442
443
444 /* Retrieve the value of a specified variable of given type.
445 The value is stored in the location pointed to by PTR variable.
446 VARIABLE and TYPE specify the variable name and type. If the
447 variable is not found and WARN is not null, the warning message
448 is issued.
449
450 Return value is 0 if the variable is found, 1 otherwise.
451 If PTR is not NULL, it must point to
452
453 int if TYPE is mailvar_type_number or mailvar_type_boolean
454 const char * if TYPE is mailvar_type_string.
455
456 Passing PTR=NULL may be used to check whether the variable is set
457 without retrieving its value. */
458
459 int
mailvar_get(void * ptr,const char * variable,enum mailvar_type type,int warn)460 mailvar_get (void *ptr, const char *variable, enum mailvar_type type, int warn)
461 {
462 struct mailvar_variable *var = mailvar_find_variable (variable, 0);
463
464 if (!var->set || (type != mailvar_type_whatever && var->type != type))
465 {
466 if (warn)
467 mu_error (_("No value set for \"%s\""), variable);
468 return 1;
469 }
470 if (ptr)
471 switch (type)
472 {
473 case mailvar_type_string:
474 *(char**)ptr = var->value.string;
475 break;
476
477 case mailvar_type_number:
478 *(int*)ptr = var->value.number;
479 break;
480
481 case mailvar_type_boolean:
482 *(int*)ptr = var->value.bool;
483 break;
484
485 default:
486 break;
487 }
488
489 return 0;
490 }
491
492 int
mailvar_is_true(char const * name)493 mailvar_is_true (char const *name)
494 {
495 return mailvar_get (NULL, name, mailvar_type_boolean, 0) == 0;
496 }
497
498 /* Initialize mailvar_list entry: clear set indicator and free any memory
499 associated with the data */
500 void
mailvar_variable_reset(struct mailvar_variable * var)501 mailvar_variable_reset (struct mailvar_variable *var)
502 {
503 if (!var->set)
504 return;
505
506 switch (var->type)
507 {
508 case mailvar_type_string:
509 free (var->value.string);
510 var->value.string = NULL;
511 break;
512
513 default:
514 break;
515 }
516 var->set = 0;
517 }
518
519 /* Set environement
520 The mailvar_set() function adds to the mailvar_list the VARIABLE
521 with the given VALUE, if VARIABLE does not already exist.
522 If it does exist in the mailvar_list, then its value is changed
523 to VALUE if MOPTF_OVERWRITE bit is set in FLAGS, otherwise the
524 value is not changed.
525
526 Unless MOPTF_QUIET bit is set in FLAGS, the function performs
527 semantic check, using the builtin options table.
528
529 If VALUE is null the VARIABLE is unset. */
530 int
mailvar_set(const char * variable,void * value,enum mailvar_type type,int flags)531 mailvar_set (const char *variable, void *value, enum mailvar_type type,
532 int flags)
533 {
534 struct mailvar_variable *var, newvar;
535 const struct mailvar_symbol *sym = find_mailvar_symbol (variable);
536 enum mailvar_cmd cmd =
537 (flags & MOPTF_UNSET) ? mailvar_cmd_unset : mailvar_cmd_set;
538
539 if (!(flags & MOPTF_QUIET) && mailvar_is_true (mailvar_name_variable_strict))
540 {
541 if (!sym)
542 mu_diag_output (MU_DIAG_WARNING, _("setting unknown variable %s"),
543 variable);
544 else if (sym->flags & MAILVAR_RDONLY)
545 {
546 mu_error (cmd == mailvar_cmd_set
547 ? _("Cannot set read-only variable %s")
548 : _("Cannot unset read-only variable %s"),
549 variable);
550 return 1;
551 }
552 else if (!(sym->flags & MAILVAR_TYPEMASK (type))
553 && cmd == mailvar_cmd_set)
554 {
555 mu_error (_("Wrong type for %s"), variable);
556 return 1;
557 }
558 }
559
560 var = mailvar_find_variable (variable, cmd == mailvar_cmd_set);
561
562 if (!var || (var->set && !(flags & MOPTF_OVERWRITE)))
563 return 0;
564
565 newvar.name = var->name;
566 newvar.type = var->type;
567 newvar.set = 0;
568 memset (&newvar.value, 0, sizeof (newvar.value));
569
570 switch (cmd)
571 {
572 case mailvar_cmd_set:
573 if (value)
574 {
575 switch (type)
576 {
577 case mailvar_type_number:
578 newvar.value.number = *(int*)value;
579 break;
580
581 case mailvar_type_string:
582 {
583 char *p = strdup (value);
584 if (!p)
585 {
586 mu_error ("%s", _("Not enough memory"));
587 return 1;
588 }
589 newvar.value.string = p;
590 }
591 break;
592
593 case mailvar_type_boolean:
594 newvar.value.bool = *(int*)value;
595 break;
596
597 default:
598 abort();
599 }
600 newvar.set = 1;
601 }
602 newvar.type = type;
603 if (sym
604 && sym->handler
605 && sym->flags & MAILVAR_TYPEMASK (type)
606 && sym->handler (cmd, &newvar))
607 {
608 mailvar_variable_reset (&newvar);
609 return 1;
610 }
611 mailvar_variable_reset (var);
612 *var = newvar;
613 break;
614
615 case mailvar_cmd_unset:
616 if (sym
617 && sym->handler
618 && sym->handler (cmd, var))
619 return 1;
620 mailvar_variable_reset (var);
621 }
622
623 return 0;
624 }
625
626 static int
set_folder(enum mailvar_cmd cmd,struct mailvar_variable * var)627 set_folder (enum mailvar_cmd cmd, struct mailvar_variable *var)
628 {
629 int rc;
630
631 if (var->value.string)
632 {
633 char *p = mu_tilde_expansion (var->value.string, MU_HIERARCHY_DELIMITER,
634 NULL);
635 if (!p)
636 mu_alloc_die ();
637 if (var->set)
638 free (var->value.string);
639 var->value.string = p;
640 }
641
642 rc = mu_set_folder_directory (var->value.string);
643 if (rc)
644 mu_diag_funcall (MU_DIAG_ERROR, "mu_set_folder_directory",
645 var->value.string, rc);
646 return rc;
647 }
648
649
650 static int
set_headline(enum mailvar_cmd cmd,struct mailvar_variable * var)651 set_headline (enum mailvar_cmd cmd, struct mailvar_variable *var)
652 {
653 if (cmd == mailvar_cmd_unset)
654 return 1;
655
656 mail_compile_headline (var->value.string);
657 return 0;
658 }
659
660 static int
set_decode_fallback(enum mailvar_cmd cmd,struct mailvar_variable * var)661 set_decode_fallback (enum mailvar_cmd cmd, struct mailvar_variable *var)
662 {
663 char *value;
664 int rc;
665
666 switch (cmd)
667 {
668 case mailvar_cmd_set:
669 value = var->value.string;
670 break;
671
672 case mailvar_cmd_unset:
673 value = "none";
674 }
675
676 rc = mu_set_default_fallback (value);
677 if (rc)
678 mu_error (_("Incorrect value for decode-fallback"));
679 return rc;
680 }
681
682 static int
set_replyregex(enum mailvar_cmd cmd,struct mailvar_variable * var)683 set_replyregex (enum mailvar_cmd cmd, struct mailvar_variable *var)
684 {
685 int rc;
686 char *err;
687
688 switch (cmd)
689 {
690 case mailvar_cmd_set:
691 if ((rc = mu_unre_set_regex (var->value.string, 0, &err)))
692 {
693 if (err)
694 mu_error ("%s: %s", mu_strerror (rc), err);
695 else
696 mu_error ("%s", mu_strerror (rc));
697 return 1;
698 }
699 break;
700
701 case mailvar_cmd_unset:
702 return 1;
703 }
704
705 return 0;
706 }
707
708 static int
set_screen(enum mailvar_cmd cmd,struct mailvar_variable * var)709 set_screen (enum mailvar_cmd cmd, struct mailvar_variable *var)
710 {
711 if (cmd == mailvar_cmd_set)
712 page_invalidate (1);
713 return 0;
714 }
715
716 #define DEFAULT_DEBUG_LEVEL MU_DEBUG_LEVEL_UPTO (MU_DEBUG_TRACE7)
717
718 static int
set_verbose(enum mailvar_cmd cmd,struct mailvar_variable * var)719 set_verbose (enum mailvar_cmd cmd, struct mailvar_variable *var)
720 {
721 switch (cmd)
722 {
723 case mailvar_cmd_set:
724 mu_debug_set_category_level (MU_DEBCAT_APP, DEFAULT_DEBUG_LEVEL);
725 break;
726
727 case mailvar_cmd_unset:
728 mu_debug_set_category_level (MU_DEBCAT_APP, 0);
729 }
730 return 0;
731 }
732
733 static int
set_debug(enum mailvar_cmd cmd,struct mailvar_variable * var)734 set_debug (enum mailvar_cmd cmd, struct mailvar_variable *var)
735 {
736 mu_debug_clear_all ();
737
738 if (cmd == mailvar_cmd_set)
739 {
740 if (var->type == mailvar_type_boolean)
741 {
742 if (var->set)
743 mu_debug_set_category_level (MU_DEBCAT_ALL, DEFAULT_DEBUG_LEVEL);
744 return 0;
745 }
746 mu_debug_parse_spec (var->value.string);
747 }
748 return 0;
749 }
750
751 static int
set_outfilename(enum mailvar_cmd cmd,struct mailvar_variable * var)752 set_outfilename (enum mailvar_cmd cmd, struct mailvar_variable *var)
753 {
754 static struct mu_kwd kwtab[] = {
755 { "local", outfilename_local },
756 { "email", outfilename_email },
757 { "domain", outfilename_domain },
758 { NULL }
759 };
760
761 switch (cmd)
762 {
763 case mailvar_cmd_set:
764 if (mu_kwd_xlat_name (kwtab, var->value.string, &outfilename_mode))
765 {
766 mu_error (_("unsupported value for %s"), var->name);
767 return 1;
768 }
769 break;
770
771 default:
772 return 1;
773 }
774 return 0;
775 }
776
777 static int
set_escape(enum mailvar_cmd cmd,struct mailvar_variable * var)778 set_escape (enum mailvar_cmd cmd, struct mailvar_variable *var)
779 {
780 if (cmd == mailvar_cmd_set)
781 {
782 if (var->value.string[0] == 0)
783 {
784 mu_error (_("escape character cannot be empty"));
785 return 1;
786 }
787 else if (var->value.string[1] != 0)
788 {
789 mu_error (_("escape value must be a single character"));
790 return 1;
791 }
792 }
793 return 0;
794 }
795
796 size_t
_mailvar_symbol_count(int set)797 _mailvar_symbol_count (int set)
798 {
799 if (!set)
800 return mailvar_symbol_count;
801 else
802 {
803 struct mailvar_symbol *s;
804 size_t count = 0;
805 for (s = mailvar_tab; s->var.name; s++)
806 if (s->var.set)
807 count++;
808 return count;
809 }
810 }
811
812 static int
mailvar_mapper(void ** itmv,size_t itmc,void * call_data)813 mailvar_mapper (void **itmv, size_t itmc, void *call_data)
814 {
815 return MU_LIST_MAP_OK;
816 }
817
818 int
_mailvar_symbol_to_list(int set,mu_list_t list)819 _mailvar_symbol_to_list (int set, mu_list_t list)
820 {
821 struct mailvar_symbol *s;
822 for (s = mailvar_tab; s->var.name; s++)
823 if (!set || s->var.set)
824 mu_list_append (list, &s->var);
825 return 0;
826 }
827
828 mu_list_t
mailvar_list_copy(int set)829 mailvar_list_copy (int set)
830 {
831 mu_list_t list = NULL;
832
833 if (mailvar_list)
834 mu_list_map (mailvar_list, mailvar_mapper, NULL, 1, &list);
835 if (!list)
836 mu_list_create (&list);
837 _mailvar_symbol_to_list (set, list);
838 mu_list_sort (list, mailvar_variable_comp);
839 return list;
840 }
841
842
843 enum
844 {
845 MAILVAR_ITR_ALL = 0, /* Return all variables */
846 MAILVAR_ITR_SET = 0x1, /* Return only variables that have been set */
847 MAILVAR_ITR_WRITABLE = 0x2 /* Return only writable variables */
848 };
849
850 struct mailvar_iterator
851 {
852 int flags; /* MAILVAR_ITR_ bits */
853 const char *prefix; /* Prefix to match */
854 int prefixlen; /* Length of the prefix */
855 mu_list_t varlist; /* List of collected variables */
856 mu_iterator_t varitr; /* Iterator over varlist */
857 };
858
859 /* Return next match from ITR */
860 const char *
mailvar_iterate_next(struct mailvar_iterator * itr)861 mailvar_iterate_next (struct mailvar_iterator *itr)
862 {
863 struct mailvar_variable *vp;
864
865 while (!mu_iterator_is_done (itr->varitr))
866 {
867 size_t len;
868
869 mu_iterator_current (itr->varitr, (void**) &vp);
870 mu_iterator_next (itr->varitr);
871
872 if (itr->flags & MAILVAR_ITR_WRITABLE)
873 {
874 const struct mailvar_symbol *sym = find_mailvar_symbol (vp->name);
875 if (sym && (sym->flags & MAILVAR_RDONLY))
876 continue;
877 }
878
879 if (strlen (vp->name) >= itr->prefixlen
880 && strncmp (vp->name, itr->prefix, itr->prefixlen) == 0)
881 return strdup (vp->name);
882
883 /* See if it's a negated boolean */
884 if (itr->prefixlen >= 2 && memcmp (itr->prefix, "no", 2) == 0
885 && (len = strlen (vp->name)) >= itr->prefixlen - 2
886 && memcmp (vp->name, itr->prefix + 2, itr->prefixlen - 2) == 0)
887 {
888 char *p;
889 struct mailvar_symbol *sym = find_mailvar_symbol (vp->name);
890 if (sym && !(sym->flags & (MAILVAR_TYPEMASK (mailvar_type_boolean))))
891 continue;
892 p = malloc (len + 3);
893 if (p)
894 {
895 strcpy (p, "no");
896 strcpy (p + 2, vp->name);
897 }
898 return p;
899 }
900 }
901 return NULL;
902 }
903
904 /* Initialize iterator, return the name of the first match */
905 const char *
mailvar_iterate_first(int flags,const char * prefix,struct mailvar_iterator ** pitr)906 mailvar_iterate_first (int flags,
907 const char *prefix, struct mailvar_iterator **pitr)
908 {
909 struct mailvar_iterator *itr = mu_alloc (sizeof *itr);
910 itr->flags = flags;
911 itr->prefix = prefix;
912 itr->prefixlen = strlen (prefix);
913 itr->varlist = mailvar_list_copy (flags & MAILVAR_ITR_SET);
914 mu_list_get_iterator (itr->varlist, &itr->varitr);
915 mu_iterator_first (itr->varitr);
916 *pitr = itr;
917 return mailvar_iterate_next (itr);
918 }
919
920 /* Release memory used by ITR */
921 void
mailvar_iterate_end(struct mailvar_iterator ** pitr)922 mailvar_iterate_end (struct mailvar_iterator **pitr)
923 {
924 if (pitr && *pitr)
925 {
926 struct mailvar_iterator *itr = *pitr;
927 mu_iterator_destroy (&itr->varitr);
928 mu_list_destroy (&itr->varlist);
929 free (itr);
930 *pitr = NULL;
931 }
932 }
933
934 struct mailvar_print_closure
935 {
936 int prettyprint;
937 mu_stream_t out;
938 int width;
939 };
940
941 static int
mailvar_printer(void * item,void * data)942 mailvar_printer (void *item, void *data)
943 {
944 struct mailvar_variable *vp = item;
945 struct mailvar_print_closure *clos = data;
946
947 if (clos->prettyprint)
948 {
949 const struct mailvar_symbol *sym = find_mailvar_symbol (vp->name);
950
951 if (sym)
952 {
953 if (sym->flags & MAILVAR_HIDDEN)
954 return 0;
955 if (sym->flags & MAILVAR_RDONLY)
956 mu_stream_printf (clos->out, "# %s:\n", _("Read-only variable"));
957 print_descr (clos->out, gettext (sym->descr), 1, 3,
958 clos->width - 1, "# ");
959 }
960 }
961 switch (vp->type)
962 {
963 case mailvar_type_number:
964 mu_stream_printf (clos->out, "%s=%d", vp->name, vp->value.number);
965 break;
966
967 case mailvar_type_string:
968 mu_stream_printf (clos->out, "%s=\"%s\"", vp->name, vp->value.string);
969 break;
970
971 case mailvar_type_boolean:
972 if (!vp->value.bool)
973 mu_stream_printf (clos->out, "no");
974 mu_stream_printf (clos->out, "%s", vp->name);
975 break;
976
977 case mailvar_type_whatever:
978 mu_stream_printf (clos->out, "%s %s", vp->name, _("oops?"));
979 }
980 mu_stream_printf (clos->out, "\n");
981 return 0;
982 }
983
984 void
mailvar_print(int set)985 mailvar_print (int set)
986 {
987 mu_list_t varlist;
988 size_t count;
989 struct mailvar_print_closure clos;
990
991 varlist = mailvar_list_copy (set);
992 mu_list_count (varlist, &count);
993 clos.out = open_pager (count);
994 clos.prettyprint = mailvar_is_true (mailvar_name_variable_pretty_print);
995 clos.width = util_screen_columns ();
996
997 mu_list_foreach (varlist, mailvar_printer, &clos);
998 mu_list_destroy (&varlist);
999 mu_stream_unref (clos.out);
1000 }
1001
1002
1003 void
mailvar_variable_format(mu_stream_t stream,const struct mailvar_variable * var,const char * defval)1004 mailvar_variable_format (mu_stream_t stream,
1005 const struct mailvar_variable *var,
1006 const char *defval)
1007 {
1008 if (var)
1009 switch (var->type)
1010 {
1011 case mailvar_type_string:
1012 mu_stream_printf (stream, "%s", var->value.string);
1013 break;
1014
1015 case mailvar_type_number:
1016 mu_stream_printf (stream, "%d", var->value.number);
1017 break;
1018
1019 case mailvar_type_boolean:
1020 mu_stream_printf (stream, "%s", var->set ? "yes" : "no");
1021 break;
1022
1023 default:
1024 if (defval)
1025 mu_stream_printf (stream, "%s", defval);
1026 break;
1027 }
1028 }
1029
1030
1031 static char *typestr[] =
1032 {
1033 N_("untyped"),
1034 N_("numeric"),
1035 N_("string"),
1036 N_("boolean")
1037 };
1038
1039 static void
describe_symbol(mu_stream_t out,int width,const struct mailvar_symbol * sym)1040 describe_symbol (mu_stream_t out, int width, const struct mailvar_symbol *sym)
1041 {
1042 int i, t;
1043 const struct mailvar_symbol *ali;
1044 mu_stream_stat_buffer stat;
1045
1046 mu_stream_set_stat (out, MU_STREAM_STAT_MASK (MU_STREAM_STAT_OUT),
1047 stat);
1048 mu_stream_printf (out, "%s", sym->var.name);
1049 for (ali = sym + 1; ali->var.name && ali->flags & MAILVAR_ALIAS; ali++)
1050 {
1051 size_t len = strlen (ali->var.name) + 2;
1052 if (stat[MU_STREAM_STAT_OUT] + len > width)
1053 {
1054 stat[MU_STREAM_STAT_OUT] = 0;
1055 mu_stream_printf (out, "\n%s", ali->var.name);
1056 }
1057 else
1058 mu_stream_printf (out, ", %s", ali->var.name);
1059 }
1060 mu_stream_printf (out, "\n");
1061 mu_stream_set_stat (out, 0, NULL);
1062
1063 mu_stream_printf (out, _("Type: "));
1064 for (i = 0, t = 0; i < sizeof (typestr) / sizeof (typestr[0]); i++)
1065 if (sym->flags & MAILVAR_TYPEMASK (i))
1066 {
1067 if (t++)
1068 mu_stream_printf (out, " %s ", _("or"));
1069 mu_stream_printf (out, "%s", gettext (typestr[i]));
1070 }
1071 if (!t)
1072 mu_stream_printf (out, "%s", gettext (typestr[0]));
1073 mu_stream_printf (out, "\n");
1074
1075 mu_stream_printf (out, "%s", _("Current value: "));
1076 mailvar_variable_format (out, &sym->var, _("[not set]"));
1077
1078 if (sym->flags & MAILVAR_RDONLY)
1079 mu_stream_printf (out, " [%s]", _("read-only"));
1080 mu_stream_printf (out, "\n");
1081
1082 print_descr (out, gettext (sym->descr ? sym->descr : N_("Not documented")),
1083 1, 1, width - 1, NULL);
1084 mu_stream_printf (out, "\n");
1085 }
1086
1087 int
mail_variable(int argc,char ** argv)1088 mail_variable (int argc, char **argv)
1089 {
1090 int pagelines = util_get_crt ();
1091 int width = util_screen_columns ();
1092 mu_stream_t out = open_pager (pagelines + 1);
1093
1094 if (argc == 1)
1095 {
1096 struct mailvar_symbol *sym;
1097
1098 for (sym = mailvar_tab; sym->var.name; sym++)
1099 if (!(sym->flags & (MAILVAR_HIDDEN|MAILVAR_ALIAS)))
1100 describe_symbol (out, width, sym);
1101 }
1102 else
1103 {
1104 int i;
1105
1106 for (i = 1; i < argc; i++)
1107 {
1108 struct mailvar_symbol *sym = find_mailvar_symbol (argv[i]);
1109 if (!sym)
1110 mu_stream_printf (out, "%s: unknown\n", argv[i]);
1111 else
1112 describe_symbol (out, width, sym);
1113 }
1114 }
1115 mu_stream_unref (out);
1116 return 0;
1117 }
1118
1119 #ifdef WITH_READLINE
1120 static char *
mailvar_generator(int flags,const char * text,int state)1121 mailvar_generator (int flags, const char *text, int state)
1122 {
1123 static struct mailvar_iterator *itr;
1124 const char *p;
1125
1126 if (!state)
1127 p = mailvar_iterate_first (flags, text, &itr);
1128 else
1129 p = mailvar_iterate_next (itr);
1130
1131 if (!p)
1132 {
1133 mailvar_iterate_end (&itr);
1134 return NULL;
1135 }
1136 return strdup (p);
1137 }
1138
1139 /* Completion generator for the "set" command */
1140 static char *
mailvar_set_generator(const char * text,int state)1141 mailvar_set_generator (const char *text, int state)
1142 {
1143 return mailvar_generator (MAILVAR_ITR_WRITABLE, text, state);
1144 }
1145
1146 /* Completion generator for the "unset" command */
1147 static char *
mailvar_unset_generator(const char * text,int state)1148 mailvar_unset_generator (const char *text, int state)
1149 {
1150 return mailvar_generator (MAILVAR_ITR_SET | MAILVAR_ITR_WRITABLE,
1151 text, state);
1152 }
1153
1154 /* Completion generator for the "variable" command */
1155 static char *
mailvar_variable_generator(const char * text,int state)1156 mailvar_variable_generator (const char *text, int state)
1157 {
1158 return mailvar_generator (MAILVAR_ITR_ALL, text, state);
1159 }
1160
1161 /* Completion function for all three commands */
1162 char **
mailvar_set_compl(int argc,char ** argv,int point)1163 mailvar_set_compl (int argc, char **argv, int point)
1164 {
1165 /* Possible values for argv[0] are: set, unset, variable */
1166 char **matches =
1167 rl_completion_matches (point & COMPL_WS ? "" : argv[argc-1],
1168 argv[0][0] == 'u'
1169 ? mailvar_unset_generator
1170 : argv[0][0] == 's'
1171 ? mailvar_set_generator
1172 : mailvar_variable_generator);
1173 ml_attempted_completion_over ();
1174 if (matches && matches[1] == NULL) /* Exact match */
1175 {
1176 switch (argv[0][0])
1177 {
1178 case 'u':
1179 ml_set_completion_append_character (' ');
1180 break;
1181
1182 case 's':
1183 {
1184 struct mailvar_symbol *sym = find_mailvar_symbol (matches[0]);
1185 if (sym &&
1186 (sym->flags & (MAILVAR_TYPEMASK (mailvar_type_string)
1187 | MAILVAR_TYPEMASK (mailvar_type_number))))
1188 ml_set_completion_append_character ('=');
1189 else
1190 ml_set_completion_append_character (' ');
1191 }
1192 break;
1193
1194 default:
1195 ml_set_completion_append_character (0);
1196 }
1197 }
1198
1199 return matches;
1200 }
1201
1202 #endif
1203