show/reply: Unify the code that extracts text parts
[notmuch.git] / notmuch-reply.c
1 /* notmuch - Not much of an email program, (just index and search)
2  *
3  * Copyright © 2009 Carl Worth
4  * Copyright © 2009 Keith Packard
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see http://www.gnu.org/licenses/ .
18  *
19  * Authors: Carl Worth <cworth@cworth.org>
20  *          Keith Packard <keithp@keithp.com>
21  */
22
23 #include "notmuch-client.h"
24 #include "gmime-filter-headers.h"
25
26 static void
27 reply_headers_message_part (GMimeMessage *message);
28
29 static void
30 reply_part_content (GMimeObject *part);
31
32 static const notmuch_show_format_t format_reply = {
33     "", NULL,
34         "", NULL,
35             "", NULL, reply_headers_message_part, ">\n",
36             "",
37                 NULL,
38                 NULL,
39                 NULL,
40                 reply_part_content,
41                 NULL,
42                 "",
43             "",
44         "", "",
45     ""
46 };
47
48 static void
49 show_reply_headers (GMimeMessage *message)
50 {
51     GMimeStream *stream_stdout = NULL, *stream_filter = NULL;
52
53     stream_stdout = g_mime_stream_file_new (stdout);
54     if (stream_stdout) {
55         g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
56         stream_filter = g_mime_stream_filter_new(stream_stdout);
57         if (stream_filter) {
58                 g_mime_stream_filter_add(GMIME_STREAM_FILTER(stream_filter),
59                                          g_mime_filter_headers_new());
60                 g_mime_object_write_to_stream(GMIME_OBJECT(message), stream_filter);
61                 g_object_unref(stream_filter);
62         }
63         g_object_unref(stream_stdout);
64     }
65 }
66
67 static void
68 reply_headers_message_part (GMimeMessage *message)
69 {
70     InternetAddressList *recipients;
71     const char *recipients_string;
72
73     printf ("> From: %s\n", g_mime_message_get_sender (message));
74     recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO);
75     recipients_string = internet_address_list_to_string (recipients, 0);
76     if (recipients_string)
77         printf ("> To: %s\n",
78                 recipients_string);
79     recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC);
80     recipients_string = internet_address_list_to_string (recipients, 0);
81     if (recipients_string)
82         printf ("> Cc: %s\n",
83                 recipients_string);
84     printf ("> Subject: %s\n", g_mime_message_get_subject (message));
85     printf ("> Date: %s\n", g_mime_message_get_date_as_string (message));
86 }
87
88
89 static void
90 reply_part_content (GMimeObject *part)
91 {
92     GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
93     GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (part);
94
95     if (g_mime_content_type_is_type (content_type, "multipart", "*") ||
96         g_mime_content_type_is_type (content_type, "message", "rfc822"))
97     {
98         /* Output nothing, since multipart subparts will be handled individually. */
99     }
100     else if (g_mime_content_type_is_type (content_type, "application", "pgp-encrypted") ||
101              g_mime_content_type_is_type (content_type, "application", "pgp-signature"))
102     {
103         /* Ignore PGP/MIME cruft parts */
104     }
105     else if (g_mime_content_type_is_type (content_type, "text", "*") &&
106         !g_mime_content_type_is_type (content_type, "text", "html"))
107     {
108         GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
109         g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
110         show_text_part_content (part, stream_stdout, NOTMUCH_SHOW_TEXT_PART_REPLY);
111         g_object_unref(stream_stdout);
112     }
113     else
114     {
115         if (disposition &&
116             strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
117         {
118             const char *filename = g_mime_part_get_filename (GMIME_PART (part));
119             printf ("Attachment: %s (%s)\n", filename,
120                     g_mime_content_type_to_string (content_type));
121         }
122         else
123         {
124             printf ("Non-text part: %s\n",
125                     g_mime_content_type_to_string (content_type));
126         }
127     }
128 }
129
130 /* Is the given address configured as one of the user's "personal" or
131  * "other" addresses. */
132 static int
133 address_is_users (const char *address, notmuch_config_t *config)
134 {
135     const char *primary;
136     const char **other;
137     size_t i, other_len;
138
139     primary = notmuch_config_get_user_primary_email (config);
140     if (strcasecmp (primary, address) == 0)
141         return 1;
142
143     other = notmuch_config_get_user_other_email (config, &other_len);
144     for (i = 0; i < other_len; i++)
145         if (strcasecmp (other[i], address) == 0)
146             return 1;
147
148     return 0;
149 }
150
151 /* Scan addresses in 'list'.
152  *
153  * If 'message' is non-NULL, then for each address in 'list' that is
154  * not configured as one of the user's addresses in 'config', add that
155  * address to 'message' as an address of 'type'.
156  *
157  * If 'user_from' is non-NULL and *user_from is NULL, *user_from will
158  * be set to the first address encountered in 'list' that is the
159  * user's address.
160  *
161  * Return the number of addresses added to 'message'. (If 'message' is
162  * NULL, the function returns 0 by definition.)
163  */
164 static unsigned int
165 scan_address_list (InternetAddressList *list,
166                    notmuch_config_t *config,
167                    GMimeMessage *message,
168                    GMimeRecipientType type,
169                    const char **user_from)
170 {
171     InternetAddress *address;
172     int i;
173     unsigned int n = 0;
174
175     for (i = 0; i < internet_address_list_length (list); i++) {
176         address = internet_address_list_get_address (list, i);
177         if (INTERNET_ADDRESS_IS_GROUP (address)) {
178             InternetAddressGroup *group;
179             InternetAddressList *group_list;
180
181             group = INTERNET_ADDRESS_GROUP (address);
182             group_list = internet_address_group_get_members (group);
183             if (group_list == NULL)
184                 continue;
185
186             n += scan_address_list (group_list, config, message, type, user_from);
187         } else {
188             InternetAddressMailbox *mailbox;
189             const char *name;
190             const char *addr;
191
192             mailbox = INTERNET_ADDRESS_MAILBOX (address);
193
194             name = internet_address_get_name (address);
195             addr = internet_address_mailbox_get_addr (mailbox);
196
197             if (address_is_users (addr, config)) {
198                 if (user_from && *user_from == NULL)
199                     *user_from = addr;
200             } else if (message) {
201                 g_mime_message_add_recipient (message, type, name, addr);
202                 n++;
203             }
204         }
205     }
206
207     return n;
208 }
209
210 /* Scan addresses in 'recipients'.
211  *
212  * See the documentation of scan_address_list() above. This function
213  * does exactly the same, but converts 'recipients' to an
214  * InternetAddressList first.
215  */
216 static unsigned int
217 scan_address_string (const char *recipients,
218                      notmuch_config_t *config,
219                      GMimeMessage *message,
220                      GMimeRecipientType type,
221                      const char **user_from)
222 {
223     InternetAddressList *list;
224
225     if (recipients == NULL)
226         return 0;
227
228     list = internet_address_list_parse_string (recipients);
229     if (list == NULL)
230         return 0;
231
232     return scan_address_list (list, config, message, type, user_from);
233 }
234
235 /* Does the address in the Reply-To header of 'message' already appear
236  * in either the 'To' or 'Cc' header of the message?
237  */
238 static int
239 reply_to_header_is_redundant (notmuch_message_t *message)
240 {
241     const char *reply_to, *to, *cc, *addr;
242     InternetAddressList *list;
243     InternetAddress *address;
244     InternetAddressMailbox *mailbox;
245
246     reply_to = notmuch_message_get_header (message, "reply-to");
247     if (reply_to == NULL || *reply_to == '\0')
248         return 0;
249
250     list = internet_address_list_parse_string (reply_to);
251
252     if (internet_address_list_length (list) != 1)
253         return 0;
254
255     address = internet_address_list_get_address (list, 0);
256     if (INTERNET_ADDRESS_IS_GROUP (address))
257         return 0;
258
259     mailbox = INTERNET_ADDRESS_MAILBOX (address);
260     addr = internet_address_mailbox_get_addr (mailbox);
261
262     to = notmuch_message_get_header (message, "to");
263     cc = notmuch_message_get_header (message, "cc");
264
265     if ((to && strstr (to, addr) != 0) ||
266         (cc && strstr (cc, addr) != 0))
267     {
268         return 1;
269     }
270
271     return 0;
272 }
273
274 /* Augment the recipients of 'reply' from the "Reply-to:", "From:",
275  * "To:", "Cc:", and "Bcc:" headers of 'message'.
276  *
277  * If 'reply_all' is true, use sender and all recipients, otherwise
278  * scan the headers for the first that contains something other than
279  * the user's addresses and add the recipients from this header
280  * (typically this would be reply-to-sender, but also handles reply to
281  * user's own message in a sensible way).
282  *
283  * If any of the user's addresses were found in these headers, the
284  * first of these returned, otherwise NULL is returned.
285  */
286 static const char *
287 add_recipients_from_message (GMimeMessage *reply,
288                              notmuch_config_t *config,
289                              notmuch_message_t *message,
290                              notmuch_bool_t reply_all)
291 {
292     struct {
293         const char *header;
294         const char *fallback;
295         GMimeRecipientType recipient_type;
296     } reply_to_map[] = {
297         { "reply-to", "from", GMIME_RECIPIENT_TYPE_TO  },
298         { "to",         NULL, GMIME_RECIPIENT_TYPE_TO  },
299         { "cc",         NULL, GMIME_RECIPIENT_TYPE_CC  },
300         { "bcc",        NULL, GMIME_RECIPIENT_TYPE_BCC }
301     };
302     const char *from_addr = NULL;
303     unsigned int i;
304     unsigned int n = 0;
305
306     /* Some mailing lists munge the Reply-To header despite it being A Bad
307      * Thing, see http://www.unicom.com/pw/reply-to-harmful.html
308      *
309      * The munging is easy to detect, because it results in a
310      * redundant reply-to header, (with an address that already exists
311      * in either To or Cc). So in this case, we ignore the Reply-To
312      * field and use the From header. This ensures the original sender
313      * will get the reply even if not subscribed to the list. Note
314      * that the address in the Reply-To header will always appear in
315      * the reply.
316      */
317     if (reply_to_header_is_redundant (message)) {
318         reply_to_map[0].header = "from";
319         reply_to_map[0].fallback = NULL;
320     }
321
322     for (i = 0; i < ARRAY_SIZE (reply_to_map); i++) {
323         const char *recipients;
324
325         recipients = notmuch_message_get_header (message,
326                                                  reply_to_map[i].header);
327         if ((recipients == NULL || recipients[0] == '\0') && reply_to_map[i].fallback)
328             recipients = notmuch_message_get_header (message,
329                                                      reply_to_map[i].fallback);
330
331         n += scan_address_string (recipients, config, reply,
332                                   reply_to_map[i].recipient_type, &from_addr);
333
334         if (!reply_all && n) {
335             /* Stop adding new recipients in reply-to-sender mode if
336              * we have added some recipient(s) above.
337              *
338              * This also handles the case of user replying to his own
339              * message, where reply-to/from is not a recipient. In
340              * this case there may be more than one recipient even if
341              * not replying to all.
342              */
343             reply = NULL;
344
345             /* From address and some recipients are enough, bail out. */
346             if (from_addr)
347                 break;
348         }
349     }
350
351     return from_addr;
352 }
353
354 static const char *
355 guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message)
356 {
357     const char *received,*primary,*by;
358     const char **other;
359     char *tohdr;
360     char *mta,*ptr,*token;
361     char *domain=NULL;
362     char *tld=NULL;
363     const char *delim=". \t";
364     size_t i,j,other_len;
365
366     const char *to_headers[] = {"Envelope-to", "X-Original-To"};
367
368     primary = notmuch_config_get_user_primary_email (config);
369     other = notmuch_config_get_user_other_email (config, &other_len);
370
371     /* sadly, there is no standard way to find out to which email
372      * address a mail was delivered - what is in the headers depends
373      * on the MTAs used along the way. So we are trying a number of
374      * heuristics which hopefully will answer this question.
375
376      * We only got here if none of the users email addresses are in
377      * the To: or Cc: header. From here we try the following in order:
378      * 1) check for an Envelope-to: header
379      * 2) check for an X-Original-To: header
380      * 3) check for a (for <email@add.res>) clause in Received: headers
381      * 4) check for the domain part of known email addresses in the
382      *    'by' part of Received headers
383      * If none of these work, we give up and return NULL
384      */
385     for (i = 0; i < sizeof(to_headers)/sizeof(*to_headers); i++) {
386         tohdr = xstrdup(notmuch_message_get_header (message, to_headers[i]));
387         if (tohdr && *tohdr) {
388             /* tohdr is potentialy a list of email addresses, so here we
389              * check if one of the email addresses is a substring of tohdr
390              */
391             if (strcasestr(tohdr, primary)) {
392                 free(tohdr);
393                 return primary;
394             }
395             for (j = 0; j < other_len; j++)
396                 if (strcasestr (tohdr, other[j])) {
397                     free(tohdr);
398                     return other[j];
399                 }
400             free(tohdr);
401         }
402     }
403
404     /* We get the concatenated Received: headers and search from the
405      * front (last Received: header added) and try to extract from
406      * them indications to which email address this message was
407      * delivered.
408      * The Received: header is special in our get_header function
409      * and is always concatenated.
410      */
411     received = notmuch_message_get_header (message, "received");
412     if (received == NULL)
413         return NULL;
414
415     /* First we look for a " for <email@add.res>" in the received
416      * header
417      */
418     ptr = strstr (received, " for ");
419     if (ptr) {
420         /* the text following is potentialy a list of email addresses,
421          * so again we check if one of the email addresses is a
422          * substring of ptr
423          */
424         if (strcasestr(ptr, primary)) {
425             return primary;
426         }
427         for (i = 0; i < other_len; i++)
428             if (strcasestr (ptr, other[i])) {
429                 return other[i];
430             }
431     }
432     /* Finally, we parse all the " by MTA ..." headers to guess the
433      * email address that this was originally delivered to.
434      * We extract just the MTA here by removing leading whitespace and
435      * assuming that the MTA name ends at the next whitespace.
436      * We test for *(by+4) to be non-'\0' to make sure there's
437      * something there at all - and then assume that the first
438      * whitespace delimited token that follows is the receiving
439      * system in this step of the receive chain
440      */
441     by = received;
442     while((by = strstr (by, " by ")) != NULL) {
443         by += 4;
444         if (*by == '\0')
445             break;
446         mta = xstrdup (by);
447         token = strtok(mta," \t");
448         if (token == NULL) {
449             free (mta);
450             break;
451         }
452         /* Now extract the last two components of the MTA host name
453          * as domain and tld.
454          */
455         domain = tld = NULL;
456         while ((ptr = strsep (&token, delim)) != NULL) {
457             if (*ptr == '\0')
458                 continue;
459             domain = tld;
460             tld = ptr;
461         }
462
463         if (domain) {
464             /* Recombine domain and tld and look for it among the configured
465              * email addresses.
466              * This time we have a known domain name and nothing else - so
467              * the test is the other way around: we check if this is a
468              * substring of one of the email addresses.
469              */
470             *(tld-1) = '.';
471
472             if (strcasestr(primary, domain)) {
473                 free(mta);
474                 return primary;
475             }
476             for (i = 0; i < other_len; i++)
477                 if (strcasestr (other[i],domain)) {
478                     free(mta);
479                     return other[i];
480                 }
481         }
482         free (mta);
483     }
484
485     return NULL;
486 }
487
488 static GMimeMessage *
489 create_reply_message(void *ctx,
490                      notmuch_config_t *config,
491                      notmuch_message_t *message,
492                      notmuch_bool_t reply_all)
493 {
494     const char *subject, *from_addr = NULL;
495     const char *in_reply_to, *orig_references, *references;
496
497     /* The 1 means we want headers in a "pretty" order. */
498     GMimeMessage *reply = g_mime_message_new (1);
499     if (reply == NULL) {
500         fprintf (stderr, "Out of memory\n");
501         return NULL;
502     }
503
504     subject = notmuch_message_get_header (message, "subject");
505     if (subject) {
506         if (strncasecmp (subject, "Re:", 3))
507             subject = talloc_asprintf (ctx, "Re: %s", subject);
508         g_mime_message_set_subject (reply, subject);
509     }
510
511     from_addr = add_recipients_from_message (reply, config,
512                                              message, reply_all);
513
514     if (from_addr == NULL)
515         from_addr = guess_from_received_header (config, message);
516
517     if (from_addr == NULL)
518         from_addr = notmuch_config_get_user_primary_email (config);
519
520     from_addr = talloc_asprintf (ctx, "%s <%s>",
521                                  notmuch_config_get_user_name (config),
522                                  from_addr);
523     g_mime_object_set_header (GMIME_OBJECT (reply),
524                               "From", from_addr);
525
526     in_reply_to = talloc_asprintf (ctx, "<%s>",
527                                    notmuch_message_get_message_id (message));
528
529     g_mime_object_set_header (GMIME_OBJECT (reply),
530                               "In-Reply-To", in_reply_to);
531
532     orig_references = notmuch_message_get_header (message, "references");
533     references = talloc_asprintf (ctx, "%s%s%s",
534                                   orig_references ? orig_references : "",
535                                   orig_references ? " " : "",
536                                   in_reply_to);
537     g_mime_object_set_header (GMIME_OBJECT (reply),
538                               "References", references);
539
540     return reply;
541 }
542
543 static int
544 notmuch_reply_format_default(void *ctx,
545                              notmuch_config_t *config,
546                              notmuch_query_t *query,
547                              notmuch_show_params_t *params,
548                              notmuch_bool_t reply_all)
549 {
550     GMimeMessage *reply;
551     notmuch_messages_t *messages;
552     notmuch_message_t *message;
553     const notmuch_show_format_t *format = &format_reply;
554
555     for (messages = notmuch_query_search_messages (query);
556          notmuch_messages_valid (messages);
557          notmuch_messages_move_to_next (messages))
558     {
559         message = notmuch_messages_get (messages);
560
561         reply = create_reply_message (ctx, config, message, reply_all);
562
563         /* If reply creation failed, we're out of memory, so don't
564          * bother trying any more messages.
565          */
566         if (!reply) {
567             notmuch_message_destroy (message);
568             return 1;
569         }
570
571         show_reply_headers (reply);
572
573         g_object_unref (G_OBJECT (reply));
574         reply = NULL;
575
576         printf ("On %s, %s wrote:\n",
577                 notmuch_message_get_header (message, "date"),
578                 notmuch_message_get_header (message, "from"));
579
580         show_message_body (message, format, params);
581
582         notmuch_message_destroy (message);
583     }
584     return 0;
585 }
586
587 static int
588 notmuch_reply_format_json(void *ctx,
589                           notmuch_config_t *config,
590                           notmuch_query_t *query,
591                           notmuch_show_params_t *params,
592                           notmuch_bool_t reply_all)
593 {
594     GMimeMessage *reply;
595     notmuch_messages_t *messages;
596     notmuch_message_t *message;
597     mime_node_t *node;
598
599     if (notmuch_query_count_messages (query) != 1) {
600         fprintf (stderr, "Error: search term did not match precisely one message.\n");
601         return 1;
602     }
603
604     messages = notmuch_query_search_messages (query);
605     message = notmuch_messages_get (messages);
606     if (mime_node_open (ctx, message, params->cryptoctx, params->decrypt,
607                         &node) != NOTMUCH_STATUS_SUCCESS)
608         return 1;
609
610     reply = create_reply_message (ctx, config, message, reply_all);
611     if (!reply)
612         return 1;
613
614     /* The headers of the reply message we've created */
615     printf ("{\"reply-headers\": ");
616     format_headers_json (ctx, reply, TRUE);
617     g_object_unref (G_OBJECT (reply));
618     reply = NULL;
619
620     /* Start the original */
621     printf (", \"original\": ");
622
623     format_part_json (ctx, node, TRUE);
624
625     /* End */
626     printf ("}\n");
627     notmuch_message_destroy (message);
628
629     return 0;
630 }
631
632 /* This format is currently tuned for a git send-email --notmuch hook */
633 static int
634 notmuch_reply_format_headers_only(void *ctx,
635                                   notmuch_config_t *config,
636                                   notmuch_query_t *query,
637                                   unused (notmuch_show_params_t *params),
638                                   notmuch_bool_t reply_all)
639 {
640     GMimeMessage *reply;
641     notmuch_messages_t *messages;
642     notmuch_message_t *message;
643     const char *in_reply_to, *orig_references, *references;
644     char *reply_headers;
645
646     for (messages = notmuch_query_search_messages (query);
647          notmuch_messages_valid (messages);
648          notmuch_messages_move_to_next (messages))
649     {
650         message = notmuch_messages_get (messages);
651
652         /* The 0 means we do not want headers in a "pretty" order. */
653         reply = g_mime_message_new (0);
654         if (reply == NULL) {
655             fprintf (stderr, "Out of memory\n");
656             return 1;
657         }
658
659         in_reply_to = talloc_asprintf (ctx, "<%s>",
660                              notmuch_message_get_message_id (message));
661
662         g_mime_object_set_header (GMIME_OBJECT (reply),
663                                   "In-Reply-To", in_reply_to);
664
665
666         orig_references = notmuch_message_get_header (message, "references");
667
668         /* We print In-Reply-To followed by References because git format-patch treats them
669          * specially.  Git does not interpret the other headers specially
670          */
671         references = talloc_asprintf (ctx, "%s%s%s",
672                                       orig_references ? orig_references : "",
673                                       orig_references ? " " : "",
674                                       in_reply_to);
675         g_mime_object_set_header (GMIME_OBJECT (reply),
676                                   "References", references);
677
678         (void)add_recipients_from_message (reply, config, message, reply_all);
679
680         reply_headers = g_mime_object_to_string (GMIME_OBJECT (reply));
681         printf ("%s", reply_headers);
682         free (reply_headers);
683
684         g_object_unref (G_OBJECT (reply));
685         reply = NULL;
686
687         notmuch_message_destroy (message);
688     }
689     return 0;
690 }
691
692 enum {
693     FORMAT_DEFAULT,
694     FORMAT_JSON,
695     FORMAT_HEADERS_ONLY,
696 };
697
698 int
699 notmuch_reply_command (void *ctx, int argc, char *argv[])
700 {
701     notmuch_config_t *config;
702     notmuch_database_t *notmuch;
703     notmuch_query_t *query;
704     char *query_string;
705     int opt_index, ret = 0;
706     int (*reply_format_func)(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params, notmuch_bool_t reply_all);
707     notmuch_show_params_t params = { .part = -1 };
708     int format = FORMAT_DEFAULT;
709     int reply_all = TRUE;
710
711     notmuch_opt_desc_t options[] = {
712         { NOTMUCH_OPT_KEYWORD, &format, "format", 'f',
713           (notmuch_keyword_t []){ { "default", FORMAT_DEFAULT },
714                                   { "json", FORMAT_JSON },
715                                   { "headers-only", FORMAT_HEADERS_ONLY },
716                                   { 0, 0 } } },
717         { NOTMUCH_OPT_KEYWORD, &reply_all, "reply-to", 'r',
718           (notmuch_keyword_t []){ { "all", TRUE },
719                                   { "sender", FALSE },
720                                   { 0, 0 } } },
721         { NOTMUCH_OPT_BOOLEAN, &params.decrypt, "decrypt", 'd', 0 },
722         { 0, 0, 0, 0, 0 }
723     };
724
725     opt_index = parse_arguments (argc, argv, options, 1);
726     if (opt_index < 0) {
727         /* diagnostics already printed */
728         return 1;
729     }
730
731     if (format == FORMAT_HEADERS_ONLY)
732         reply_format_func = notmuch_reply_format_headers_only;
733     else if (format == FORMAT_JSON)
734         reply_format_func = notmuch_reply_format_json;
735     else
736         reply_format_func = notmuch_reply_format_default;
737
738     if (params.decrypt) {
739 #ifdef GMIME_ATLEAST_26
740         /* TODO: GMimePasswordRequestFunc */
741         params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg");
742 #else
743         GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL);
744         params.cryptoctx = g_mime_gpg_context_new (session, "gpg");
745 #endif
746         if (params.cryptoctx) {
747             g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE);
748         } else {
749             params.decrypt = FALSE;
750             fprintf (stderr, "Failed to construct gpg context.\n");
751         }
752 #ifndef GMIME_ATLEAST_26
753         g_object_unref (session);
754 #endif
755     }
756
757     config = notmuch_config_open (ctx, NULL, NULL);
758     if (config == NULL)
759         return 1;
760
761     query_string = query_string_from_args (ctx, argc-opt_index, argv+opt_index);
762     if (query_string == NULL) {
763         fprintf (stderr, "Out of memory\n");
764         return 1;
765     }
766
767     if (*query_string == '\0') {
768         fprintf (stderr, "Error: notmuch reply requires at least one search term.\n");
769         return 1;
770     }
771
772     notmuch = notmuch_database_open (notmuch_config_get_database_path (config),
773                                      NOTMUCH_DATABASE_MODE_READ_ONLY);
774     if (notmuch == NULL)
775         return 1;
776
777     query = notmuch_query_create (notmuch, query_string);
778     if (query == NULL) {
779         fprintf (stderr, "Out of memory\n");
780         return 1;
781     }
782
783     if (reply_format_func (ctx, config, query, &params, reply_all) != 0)
784         return 1;
785
786     notmuch_query_destroy (query);
787     notmuch_database_close (notmuch);
788
789     if (params.cryptoctx)
790         g_object_unref(params.cryptoctx);
791
792     return ret;
793 }