mutt stable branch with some hacks
1/*
2 * Copyright (C) 1996-2000,2003,2013 Michael R. Elkins <me@mutt.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#if HAVE_CONFIG_H
20# include "config.h"
21#endif
22
23#include "mutt.h"
24#include "mutt_menu.h"
25#include "mutt_idna.h"
26#include "mapping.h"
27
28#include <string.h>
29#include <stdlib.h>
30#include <ctype.h>
31
32typedef struct query
33{
34 int num;
35 ADDRESS *addr;
36 char *name;
37 char *other;
38 struct query *next;
39} QUERY;
40
41typedef struct entry
42{
43 int tagged;
44 QUERY *data;
45} ENTRY;
46
47static const struct mapping_t QueryHelp[] = {
48 { N_("Exit"), OP_EXIT },
49 { N_("Mail"), OP_MAIL },
50 { N_("New Query"), OP_QUERY },
51 { N_("Make Alias"), OP_CREATE_ALIAS },
52 { N_("Search"), OP_SEARCH },
53 { N_("Help"), OP_HELP },
54 { NULL, 0 }
55};
56
57static void query_menu (char *buf, size_t buflen, QUERY *results, int retbuf);
58
59static ADDRESS *result_to_addr (QUERY *r)
60{
61 static ADDRESS *tmp;
62
63 if (!(tmp = rfc822_cpy_adr (r->addr, 0)))
64 return NULL;
65
66 if (!tmp->next && !tmp->personal)
67 tmp->personal = safe_strdup (r->name);
68
69 mutt_addrlist_to_intl (tmp, NULL);
70 return tmp;
71}
72
73static void free_query (QUERY **query)
74{
75 QUERY *p;
76
77 if (!query)
78 return;
79
80 while (*query)
81 {
82 p = *query;
83 *query = (*query)->next;
84
85 rfc822_free_address (&p->addr);
86 FREE (&p->name);
87 FREE (&p->other);
88 FREE (&p);
89 }
90}
91
92static QUERY *run_query (char *s, int quiet)
93{
94 FILE *fp;
95 QUERY *first = NULL;
96 QUERY *cur = NULL;
97 BUFFER *cmd = NULL;
98 char *buf = NULL;
99 size_t buflen;
100 int dummy = 0;
101 char *msg = NULL;
102 size_t msglen;
103 char *p;
104 pid_t thepid;
105
106 cmd = mutt_buffer_pool_get ();
107 mutt_expand_file_fmt (cmd, QueryCmd, s);
108
109 if ((thepid = mutt_create_filter (mutt_b2s (cmd), NULL, &fp, NULL)) < 0)
110 {
111 dprint (1, (debugfile, "unable to fork command: %s", mutt_b2s (cmd)));
112 mutt_buffer_pool_release (&cmd);
113 return 0;
114 }
115 mutt_buffer_pool_release (&cmd);
116
117 if (!quiet)
118 mutt_message _("Waiting for response...");
119
120 /* The query protocol first reads one NL-terminated line. If an error
121 * occurs, this is assumed to be an error message. Otherwise it's ignored. */
122 msg = mutt_read_line (msg, &msglen, fp, &dummy, 0);
123 while ((buf = mutt_read_line (buf, &buflen, fp, &dummy, 0)) != NULL)
124 {
125 if ((p = strtok(buf, "\t\n")))
126 {
127 if (first == NULL)
128 {
129 first = (QUERY *) safe_calloc (1, sizeof (QUERY));
130 cur = first;
131 }
132 else
133 {
134 cur->next = (QUERY *) safe_calloc (1, sizeof (QUERY));
135 cur = cur->next;
136 }
137
138 cur->addr = rfc822_parse_adrlist (cur->addr, p);
139 p = strtok(NULL, "\t\n");
140 if (p)
141 {
142 cur->name = safe_strdup (p);
143 p = strtok(NULL, "\t\n");
144 if (p)
145 cur->other = safe_strdup (p);
146 }
147 }
148 }
149 FREE (&buf);
150 safe_fclose (&fp);
151 if (mutt_wait_filter (thepid))
152 {
153 dprint (1, (debugfile, "Error: %s\n", msg));
154 if (!quiet) mutt_error ("%s", msg);
155 }
156 else
157 {
158 if (!quiet)
159 mutt_message ("%s", msg);
160 }
161 FREE (&msg);
162
163 return first;
164}
165
166static int query_search (MUTTMENU *m, regex_t *re, int n)
167{
168 ENTRY *table = (ENTRY *) m->data;
169
170 if (table[n].data->name && !regexec (re, table[n].data->name, 0, NULL, 0))
171 return 0;
172 if (table[n].data->other && !regexec (re, table[n].data->other, 0, NULL, 0))
173 return 0;
174 if (table[n].data->addr)
175 {
176 if (table[n].data->addr->personal &&
177 !regexec (re, table[n].data->addr->personal, 0, NULL, 0))
178 return 0;
179 if (table[n].data->addr->mailbox &&
180 !regexec (re, table[n].data->addr->mailbox, 0, NULL, 0))
181 return 0;
182#ifdef EXACT_ADDRESS
183 if (table[n].data->addr->val &&
184 !regexec (re, table[n].data->addr->val, 0, NULL, 0))
185 return 0;
186#endif
187 }
188
189 return REG_NOMATCH;
190}
191
192static const char * query_format_str (char *dest, size_t destlen, size_t col, int cols,
193 char op, const char *src,
194 const char *fmt, const char *ifstring,
195 const char *elsestring,
196 unsigned long data, format_flag flags)
197{
198 ENTRY *entry = (ENTRY *)data;
199 QUERY *query = entry->data;
200 char tmp[SHORT_STRING];
201 char buf2[STRING] = "";
202 int optional = (flags & MUTT_FORMAT_OPTIONAL);
203
204 switch (op)
205 {
206 case 'a':
207 rfc822_write_address (buf2, sizeof (buf2), query->addr, 1);
208 mutt_format_s (dest, destlen, fmt, buf2);
209 break;
210 case 'c':
211 snprintf (tmp, sizeof (tmp), "%%%sd", fmt);
212 snprintf (dest, destlen, tmp, query->num + 1);
213 break;
214 case 'e':
215 if (!optional)
216 mutt_format_s (dest, destlen, fmt, NONULL (query->other));
217 else if (!query->other || !*query->other)
218 optional = 0;
219 break;
220 case 'n':
221 mutt_format_s (dest, destlen, fmt, NONULL (query->name));
222 break;
223 case 't':
224 snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
225 snprintf (dest, destlen, tmp, entry->tagged ? '*' : ' ');
226 break;
227 default:
228 snprintf (tmp, sizeof (tmp), "%%%sc", fmt);
229 snprintf (dest, destlen, tmp, op);
230 break;
231 }
232
233 if (optional)
234 mutt_FormatString (dest, destlen, col, cols, ifstring, query_format_str, data, 0);
235 else if (flags & MUTT_FORMAT_OPTIONAL)
236 mutt_FormatString (dest, destlen, col, cols, elsestring, query_format_str, data, 0);
237
238 return src;
239}
240
241static void query_entry (char *s, size_t slen, MUTTMENU *m, int num)
242{
243 ENTRY *entry = &((ENTRY *) m->data)[num];
244
245 entry->data->num = num;
246 mutt_FormatString (s, slen, 0, MuttIndexWindow->cols, NONULL (QueryFormat), query_format_str,
247 (unsigned long) entry, MUTT_FORMAT_ARROWCURSOR);
248}
249
250static int query_tag (MUTTMENU *menu, int n, int m)
251{
252 ENTRY *cur = &((ENTRY *) menu->data)[n];
253 int ot = cur->tagged;
254
255 cur->tagged = m >= 0 ? m : !cur->tagged;
256 return cur->tagged - ot;
257}
258
259int mutt_query_complete (char *buf, size_t buflen)
260{
261 QUERY *results = NULL;
262 ADDRESS *tmpa;
263
264 if (!QueryCmd)
265 {
266 mutt_error _("Query command not defined.");
267 return 0;
268 }
269
270 results = run_query (buf, 1);
271 if (results)
272 {
273 /* only one response? */
274 if (results->next == NULL)
275 {
276 tmpa = result_to_addr (results);
277 mutt_addrlist_to_local (tmpa);
278 buf[0] = '\0';
279 rfc822_write_address (buf, buflen, tmpa, 0);
280 rfc822_free_address (&tmpa);
281 free_query (&results);
282 mutt_clear_error ();
283 return (0);
284 }
285 /* multiple results, choose from query menu */
286 query_menu (buf, buflen, results, 1);
287 }
288 return (0);
289}
290
291void mutt_query_menu (char *buf, size_t buflen)
292{
293 if (!QueryCmd)
294 {
295 mutt_error _("Query command not defined.");
296 return;
297 }
298
299 if (buf == NULL)
300 {
301 char buffer[STRING] = "";
302
303 query_menu (buffer, sizeof (buffer), NULL, 0);
304 }
305 else
306 {
307 query_menu (buf, buflen, NULL, 1);
308 }
309}
310
311static void query_menu (char *buf, size_t buflen, QUERY *results, int retbuf)
312{
313 MUTTMENU *menu;
314 HEADER *msg = NULL;
315 ENTRY *QueryTable = NULL;
316 QUERY *queryp = NULL;
317 int i, done = 0;
318 int op;
319 char helpstr[LONG_STRING];
320 char title[STRING];
321
322 if (results == NULL)
323 {
324 /* Prompt for Query */
325 if (mutt_get_field (_("Query: "), buf, buflen, 0) == 0 && buf[0])
326 {
327 results = run_query (buf, 0);
328 }
329 }
330
331 if (results)
332 {
333 snprintf (title, sizeof (title), _("Query '%s'"), buf);
334
335 menu = mutt_new_menu (MENU_QUERY);
336 menu->make_entry = query_entry;
337 menu->search = query_search;
338 menu->tag = query_tag;
339 menu->title = title;
340 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY, QueryHelp);
341 mutt_push_current_menu (menu);
342
343 /* count the number of results */
344 for (queryp = results; queryp; queryp = queryp->next)
345 menu->max++;
346
347 menu->data = QueryTable = (ENTRY *) safe_calloc (menu->max, sizeof (ENTRY));
348
349 for (i = 0, queryp = results; queryp; queryp = queryp->next, i++)
350 QueryTable[i].data = queryp;
351
352 while (!done)
353 {
354 switch ((op = mutt_menuLoop (menu)))
355 {
356 case OP_QUERY_APPEND:
357 case OP_QUERY:
358 if (mutt_get_field (_("Query: "), buf, buflen, 0) == 0 && buf[0])
359 {
360 QUERY *newresults = NULL;
361
362 newresults = run_query (buf, 0);
363
364 menu->redraw = REDRAW_FULL;
365 if (newresults)
366 {
367 snprintf (title, sizeof (title), _("Query '%s'"), buf);
368
369 if (op == OP_QUERY)
370 {
371 free_query (&results);
372 results = newresults;
373 FREE (&QueryTable);
374 }
375 else
376 {
377 /* append */
378 for (queryp = results; queryp->next; queryp = queryp->next);
379
380 queryp->next = newresults;
381 }
382
383
384 menu->current = 0;
385 mutt_pop_current_menu (menu);
386 mutt_menuDestroy (&menu);
387 menu = mutt_new_menu (MENU_QUERY);
388 menu->make_entry = query_entry;
389 menu->search = query_search;
390 menu->tag = query_tag;
391 menu->title = title;
392 menu->help = mutt_compile_help (helpstr, sizeof (helpstr), MENU_QUERY, QueryHelp);
393 mutt_push_current_menu (menu);
394
395 /* count the number of results */
396 for (queryp = results; queryp; queryp = queryp->next)
397 menu->max++;
398
399 if (op == OP_QUERY)
400 {
401 menu->data = QueryTable =
402 (ENTRY *) safe_calloc (menu->max, sizeof (ENTRY));
403
404 for (i = 0, queryp = results; queryp;
405 queryp = queryp->next, i++)
406 QueryTable[i].data = queryp;
407 }
408 else
409 {
410 int clear = 0;
411
412 /* append */
413 safe_realloc (&QueryTable, menu->max * sizeof (ENTRY));
414
415 menu->data = QueryTable;
416
417 for (i = 0, queryp = results; queryp;
418 queryp = queryp->next, i++)
419 {
420 /* once we hit new entries, clear/init the tag */
421 if (queryp == newresults)
422 clear = 1;
423
424 QueryTable[i].data = queryp;
425 if (clear)
426 QueryTable[i].tagged = 0;
427 }
428 }
429 }
430 }
431 break;
432
433 case OP_CREATE_ALIAS:
434 if (menu->tagprefix)
435 {
436 ADDRESS *naddr = NULL;
437
438 for (i = 0; i < menu->max; i++)
439 if (QueryTable[i].tagged)
440 {
441 ADDRESS *a = result_to_addr(QueryTable[i].data);
442 rfc822_append (&naddr, a, 0);
443 rfc822_free_address (&a);
444 }
445
446 mutt_create_alias (NULL, naddr);
447 }
448 else
449 {
450 ADDRESS *a = result_to_addr(QueryTable[menu->current].data);
451 mutt_create_alias (NULL, a);
452 rfc822_free_address (&a);
453 }
454 break;
455
456 case OP_GENERIC_SELECT_ENTRY:
457 if (retbuf)
458 {
459 done = 2;
460 break;
461 }
462 /* fall through */
463
464 case OP_MAIL:
465 msg = mutt_new_header ();
466 msg->env = mutt_new_envelope ();
467 if (!menu->tagprefix)
468 {
469 msg->env->to = result_to_addr(QueryTable[menu->current].data);
470 }
471 else
472 {
473 for (i = 0; i < menu->max; i++)
474 if (QueryTable[i].tagged)
475 {
476 ADDRESS *a = result_to_addr(QueryTable[i].data);
477 rfc822_append (&msg->env->to, a, 0);
478 rfc822_free_address (&a);
479 }
480 }
481 ci_send_message (0, msg, NULL, Context, NULL);
482 menu->redraw = REDRAW_FULL;
483 break;
484
485 case OP_EXIT:
486 done = 1;
487 break;
488 }
489 }
490
491 /* if we need to return the selected entries */
492 if (retbuf && (done == 2))
493 {
494 int tagged = 0;
495 size_t curpos = 0;
496
497 memset (buf, 0, buflen);
498
499 /* check for tagged entries */
500 for (i = 0; i < menu->max; i++)
501 {
502 if (QueryTable[i].tagged)
503 {
504 if (curpos == 0)
505 {
506 ADDRESS *tmpa = result_to_addr (QueryTable[i].data);
507 mutt_addrlist_to_local (tmpa);
508 tagged = 1;
509 rfc822_write_address (buf, buflen, tmpa, 0);
510 curpos = mutt_strlen (buf);
511 rfc822_free_address (&tmpa);
512 }
513 else if (curpos + 2 < buflen)
514 {
515 ADDRESS *tmpa = result_to_addr (QueryTable[i].data);
516 mutt_addrlist_to_local (tmpa);
517 strcat (buf, ", "); /* __STRCAT_CHECKED__ */
518 rfc822_write_address ((char *) buf + curpos + 1, buflen - curpos - 1,
519 tmpa, 0);
520 curpos = mutt_strlen (buf);
521 rfc822_free_address (&tmpa);
522 }
523 }
524 }
525 /* then enter current message */
526 if (!tagged)
527 {
528 ADDRESS *tmpa = result_to_addr (QueryTable[menu->current].data);
529 mutt_addrlist_to_local (tmpa);
530 rfc822_write_address (buf, buflen, tmpa, 0);
531 rfc822_free_address (&tmpa);
532 }
533
534 }
535
536 free_query (&results);
537 FREE (&QueryTable);
538
539 mutt_pop_current_menu (menu);
540 mutt_menuDestroy (&menu);
541 }
542}