1 /* notmuch - Not much of an email program, (just index and search)
3 * Copyright © 2009 Carl Worth
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see http://www.gnu.org/licenses/ .
18 * Author: Carl Worth <cworth@cworth.org>
21 #include "notmuch-client.h"
23 static notmuch_status_t
24 format_part_text (const void *ctx, mime_node_t *node,
25 int indent, const notmuch_show_params_t *params);
27 static const notmuch_show_format_t format_text = {
28 .message_set_start = "",
29 .part = format_part_text,
30 .message_set_sep = "",
34 static notmuch_status_t
35 format_part_json_entry (const void *ctx, mime_node_t *node,
36 int indent, const notmuch_show_params_t *params);
38 static const notmuch_show_format_t format_json = {
39 .message_set_start = "[",
40 .part = format_part_json_entry,
41 .message_set_sep = ", ",
42 .message_set_end = "]"
45 static notmuch_status_t
46 format_part_mbox (const void *ctx, mime_node_t *node,
47 int indent, const notmuch_show_params_t *params);
49 static const notmuch_show_format_t format_mbox = {
50 .message_set_start = "",
51 .part = format_part_mbox,
52 .message_set_sep = "",
56 static notmuch_status_t
57 format_part_raw (unused (const void *ctx), mime_node_t *node,
59 unused (const notmuch_show_params_t *params));
61 static const notmuch_show_format_t format_raw = {
62 .message_set_start = "",
63 .part = format_part_raw,
64 .message_set_sep = "",
69 _get_tags_as_string (const void *ctx, notmuch_message_t *message)
76 result = talloc_strdup (ctx, "");
80 for (tags = notmuch_message_get_tags (message);
81 notmuch_tags_valid (tags);
82 notmuch_tags_move_to_next (tags))
84 tag = notmuch_tags_get (tags);
86 result = talloc_asprintf_append (result, "%s%s",
87 first ? "" : " ", tag);
94 /* Get a nice, single-line summary of message. */
96 _get_one_line_summary (const void *ctx, notmuch_message_t *message)
100 const char *relative_date;
103 from = notmuch_message_get_header (message, "from");
105 date = notmuch_message_get_date (message);
106 relative_date = notmuch_time_relative_date (ctx, date);
108 tags = _get_tags_as_string (ctx, message);
110 return talloc_asprintf (ctx, "%s (%s) (%s)",
111 from, relative_date, tags);
115 format_message_json (const void *ctx, notmuch_message_t *message)
117 notmuch_tags_t *tags;
119 void *ctx_quote = talloc_new (ctx);
121 const char *relative_date;
123 date = notmuch_message_get_date (message);
124 relative_date = notmuch_time_relative_date (ctx, date);
126 printf ("\"id\": %s, \"match\": %s, \"excluded\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [",
127 json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
128 notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
129 notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? "true" : "false",
130 json_quote_str (ctx_quote, notmuch_message_get_filename (message)),
131 date, relative_date);
133 for (tags = notmuch_message_get_tags (message);
134 notmuch_tags_valid (tags);
135 notmuch_tags_move_to_next (tags))
137 printf("%s%s", first ? "" : ",",
138 json_quote_str (ctx_quote, notmuch_tags_get (tags)));
142 talloc_free (ctx_quote);
145 /* Extract just the email address from the contents of a From:
148 _extract_email_address (const void *ctx, const char *from)
150 InternetAddressList *addresses;
151 InternetAddress *address;
152 InternetAddressMailbox *mailbox;
153 const char *email = "MAILER-DAEMON";
155 addresses = internet_address_list_parse_string (from);
157 /* Bail if there is no address here. */
158 if (addresses == NULL || internet_address_list_length (addresses) < 1)
161 /* Otherwise, just use the first address. */
162 address = internet_address_list_get_address (addresses, 0);
164 /* The From header should never contain an address group rather
165 * than a mailbox. So bail if it does. */
166 if (! INTERNET_ADDRESS_IS_MAILBOX (address))
169 mailbox = INTERNET_ADDRESS_MAILBOX (address);
170 email = internet_address_mailbox_get_addr (mailbox);
171 email = talloc_strdup (ctx, email);
175 g_object_unref (addresses);
180 /* Return 1 if 'line' is an mbox From_ line---that is, a line
181 * beginning with zero or more '>' characters followed by the
182 * characters 'F', 'r', 'o', 'm', and space.
184 * Any characters at all may appear after that in the line.
187 _is_from_line (const char *line)
189 const char *s = line;
197 if (STRNCMP_LITERAL (s, "From ") == 0)
204 format_headers_json (const void *ctx, GMimeMessage *message, notmuch_bool_t reply)
206 void *local = talloc_new (ctx);
207 InternetAddressList *recipients;
208 const char *recipients_string;
211 json_quote_str (local, "Subject"),
212 json_quote_str (local, g_mime_message_get_subject (message)));
214 json_quote_str (local, "From"),
215 json_quote_str (local, g_mime_message_get_sender (message)));
216 recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO);
217 recipients_string = internet_address_list_to_string (recipients, 0);
218 if (recipients_string)
220 json_quote_str (local, "To"),
221 json_quote_str (local, recipients_string));
222 recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC);
223 recipients_string = internet_address_list_to_string (recipients, 0);
224 if (recipients_string)
226 json_quote_str (local, "Cc"),
227 json_quote_str (local, recipients_string));
231 json_quote_str (local, "In-reply-to"),
232 json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to")));
235 json_quote_str (local, "References"),
236 json_quote_str (local, g_mime_object_get_header (GMIME_OBJECT (message), "References")));
239 json_quote_str (local, "Date"),
240 json_quote_str (local, g_mime_message_get_date_as_string (message)));
248 /* Write a MIME text part out to the given stream.
250 * Both line-ending conversion (CRLF->LF) and charset conversion ( ->
251 * UTF-8) will be performed, so it is inappropriate to call this
252 * function with a non-text part. Doing so will trigger an internal
256 show_text_part_content (GMimeObject *part, GMimeStream *stream_out)
258 GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
259 GMimeStream *stream_filter = NULL;
260 GMimeDataWrapper *wrapper;
263 if (! g_mime_content_type_is_type (content_type, "text", "*"))
264 INTERNAL_ERROR ("Illegal request to format non-text part (%s) as text.",
265 g_mime_content_type_to_string (content_type));
267 if (stream_out == NULL)
270 stream_filter = g_mime_stream_filter_new (stream_out);
271 g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
272 g_mime_filter_crlf_new (FALSE, FALSE));
274 charset = g_mime_object_get_content_type_parameter (part, "charset");
276 GMimeFilter *charset_filter;
277 charset_filter = g_mime_filter_charset_new (charset, "UTF-8");
278 /* This result can be NULL for things like "unknown-8bit".
279 * Don't set a NULL filter as that makes GMime print
280 * annoying assertion-failure messages on stderr. */
281 if (charset_filter) {
282 g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
284 g_object_unref (charset_filter);
289 wrapper = g_mime_part_get_content_object (GMIME_PART (part));
290 if (wrapper && stream_filter)
291 g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
293 g_object_unref(stream_filter);
296 #ifdef GMIME_ATLEAST_26
298 signature_status_to_string (GMimeSignatureStatus x)
301 case GMIME_SIGNATURE_STATUS_GOOD:
303 case GMIME_SIGNATURE_STATUS_BAD:
305 case GMIME_SIGNATURE_STATUS_ERROR:
312 signer_status_to_string (GMimeSignerStatus x)
315 case GMIME_SIGNER_STATUS_NONE:
317 case GMIME_SIGNER_STATUS_GOOD:
319 case GMIME_SIGNER_STATUS_BAD:
321 case GMIME_SIGNER_STATUS_ERROR:
328 #ifdef GMIME_ATLEAST_26
330 format_part_sigstatus_json (mime_node_t *node)
332 GMimeSignatureList *siglist = node->sig_list;
341 void *ctx_quote = talloc_new (NULL);
343 for (i = 0; i < g_mime_signature_list_length (siglist); i++) {
344 GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i);
352 GMimeSignatureStatus status = g_mime_signature_get_status (signature);
353 printf ("\"status\": %s",
354 json_quote_str (ctx_quote,
355 signature_status_to_string (status)));
357 GMimeCertificate *certificate = g_mime_signature_get_certificate (signature);
358 if (status == GMIME_SIGNATURE_STATUS_GOOD) {
360 printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, g_mime_certificate_get_fingerprint (certificate)));
361 /* these dates are seconds since the epoch; should we
362 * provide a more human-readable format string? */
363 time_t created = g_mime_signature_get_created (signature);
365 printf (", \"created\": %d", (int) created);
366 time_t expires = g_mime_signature_get_expires (signature);
368 printf (", \"expires\": %d", (int) expires);
369 /* output user id only if validity is FULL or ULTIMATE. */
370 /* note that gmime is using the term "trust" here, which
371 * is WRONG. It's actually user id "validity". */
373 const char *name = g_mime_certificate_get_name (certificate);
374 GMimeCertificateTrust trust = g_mime_certificate_get_trust (certificate);
375 if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE))
376 printf (", \"userid\": %s", json_quote_str (ctx_quote, name));
378 } else if (certificate) {
379 const char *key_id = g_mime_certificate_get_key_id (certificate);
381 printf (", \"keyid\": %s", json_quote_str (ctx_quote, key_id));
384 GMimeSignatureError errors = g_mime_signature_get_errors (signature);
385 if (errors != GMIME_SIGNATURE_ERROR_NONE) {
386 printf (", \"errors\": %d", errors);
394 talloc_free (ctx_quote);
398 format_part_sigstatus_json (mime_node_t *node)
400 const GMimeSignatureValidity* validity = node->sig_validity;
409 const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);
411 void *ctx_quote = talloc_new (NULL);
422 printf ("\"status\": %s",
423 json_quote_str (ctx_quote,
424 signer_status_to_string (signer->status)));
426 if (signer->status == GMIME_SIGNER_STATUS_GOOD)
428 if (signer->fingerprint)
429 printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, signer->fingerprint));
430 /* these dates are seconds since the epoch; should we
431 * provide a more human-readable format string? */
433 printf (", \"created\": %d", (int) signer->created);
435 printf (", \"expires\": %d", (int) signer->expires);
436 /* output user id only if validity is FULL or ULTIMATE. */
437 /* note that gmime is using the term "trust" here, which
438 * is WRONG. It's actually user id "validity". */
439 if ((signer->name) && (signer->trust)) {
440 if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE))
441 printf (", \"userid\": %s", json_quote_str (ctx_quote, signer->name));
445 printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid));
447 if (signer->errors != GMIME_SIGNER_ERROR_NONE) {
448 printf (", \"errors\": %d", signer->errors);
452 signer = signer->next;
457 talloc_free (ctx_quote);
461 static notmuch_status_t
462 format_part_text (const void *ctx, mime_node_t *node,
463 int indent, const notmuch_show_params_t *params)
465 /* The disposition and content-type metadata are associated with
466 * the envelope for message parts */
467 GMimeObject *meta = node->envelope_part ?
468 GMIME_OBJECT (node->envelope_part) : node->part;
469 GMimeContentType *content_type = g_mime_object_get_content_type (meta);
470 const notmuch_bool_t leaf = GMIME_IS_PART (node->part);
471 const char *part_type;
474 if (node->envelope_file) {
475 notmuch_message_t *message = node->envelope_file;
477 part_type = "message";
478 printf ("\f%s{ id:%s depth:%d match:%d excluded:%d filename:%s\n",
480 notmuch_message_get_message_id (message),
482 notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0,
483 notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0,
484 notmuch_message_get_filename (message));
486 GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta);
487 const char *cid = g_mime_object_get_content_id (meta);
488 const char *filename = leaf ?
489 g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
492 strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
493 part_type = "attachment";
497 printf ("\f%s{ ID: %d", part_type, node->part_num);
499 printf (", Filename: %s", filename);
501 printf (", Content-id: %s", cid);
502 printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type));
505 if (GMIME_IS_MESSAGE (node->part)) {
506 GMimeMessage *message = GMIME_MESSAGE (node->part);
507 InternetAddressList *recipients;
508 const char *recipients_string;
510 printf ("\fheader{\n");
511 if (node->envelope_file)
512 printf ("%s\n", _get_one_line_summary (ctx, node->envelope_file));
513 printf ("Subject: %s\n", g_mime_message_get_subject (message));
514 printf ("From: %s\n", g_mime_message_get_sender (message));
515 recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO);
516 recipients_string = internet_address_list_to_string (recipients, 0);
517 if (recipients_string)
518 printf ("To: %s\n", recipients_string);
519 recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC);
520 recipients_string = internet_address_list_to_string (recipients, 0);
521 if (recipients_string)
522 printf ("Cc: %s\n", recipients_string);
523 printf ("Date: %s\n", g_mime_message_get_date_as_string (message));
524 printf ("\fheader}\n");
526 printf ("\fbody{\n");
530 if (g_mime_content_type_is_type (content_type, "text", "*") &&
531 !g_mime_content_type_is_type (content_type, "text", "html"))
533 GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
534 g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
535 show_text_part_content (node->part, stream_stdout);
536 g_object_unref(stream_stdout);
538 printf ("Non-text part: %s\n",
539 g_mime_content_type_to_string (content_type));
543 for (i = 0; i < node->nchildren; i++)
544 format_part_text (ctx, mime_node_child (node, i), indent, params);
546 if (GMIME_IS_MESSAGE (node->part))
547 printf ("\fbody}\n");
549 printf ("\f%s}\n", part_type);
551 return NOTMUCH_STATUS_SUCCESS;
555 format_part_json (const void *ctx, mime_node_t *node, notmuch_bool_t first)
557 /* Any changes to the JSON format should be reflected in the file
560 if (node->envelope_file) {
562 format_message_json (ctx, node->envelope_file);
564 printf ("\"headers\": ");
565 format_headers_json (ctx, GMIME_MESSAGE (node->part), FALSE);
567 printf (", \"body\": [");
568 format_part_json (ctx, mime_node_child (node, 0), first);
574 void *local = talloc_new (ctx);
575 /* The disposition and content-type metadata are associated with
576 * the envelope for message parts */
577 GMimeObject *meta = node->envelope_part ?
578 GMIME_OBJECT (node->envelope_part) : node->part;
579 GMimeContentType *content_type = g_mime_object_get_content_type (meta);
580 const char *cid = g_mime_object_get_content_id (meta);
581 const char *filename = GMIME_IS_PART (node->part) ?
582 g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
583 const char *terminator = "";
589 printf ("{\"id\": %d", node->part_num);
591 if (node->decrypt_attempted)
592 printf (", \"encstatus\": [{\"status\": \"%s\"}]",
593 node->decrypt_success ? "good" : "bad");
595 if (node->verify_attempted) {
596 printf (", \"sigstatus\": ");
597 format_part_sigstatus_json (node);
600 printf (", \"content-type\": %s",
601 json_quote_str (local, g_mime_content_type_to_string (content_type)));
604 printf (", \"content-id\": %s", json_quote_str (local, cid));
607 printf (", \"filename\": %s", json_quote_str (local, filename));
609 if (GMIME_IS_PART (node->part)) {
610 /* For non-HTML text parts, we include the content in the
611 * JSON. Since JSON must be Unicode, we handle charset
612 * decoding here and do not report a charset to the caller.
613 * For text/html parts, we do not include the content. If a
614 * caller is interested in text/html parts, it should retrieve
615 * them separately and they will not be decoded. Since this
616 * makes charset decoding the responsibility on the caller, we
617 * report the charset for text/html parts.
619 if (g_mime_content_type_is_type (content_type, "text", "html")) {
620 const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset");
622 if (content_charset != NULL)
623 printf (", \"content-charset\": %s", json_quote_str (local, content_charset));
624 } else if (g_mime_content_type_is_type (content_type, "text", "*")) {
625 GMimeStream *stream_memory = g_mime_stream_mem_new ();
626 GByteArray *part_content;
627 show_text_part_content (node->part, stream_memory);
628 part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
630 printf (", \"content\": %s", json_quote_chararray (local, (char *) part_content->data, part_content->len));
631 g_object_unref (stream_memory);
633 } else if (GMIME_IS_MULTIPART (node->part)) {
634 printf (", \"content\": [");
636 } else if (GMIME_IS_MESSAGE (node->part)) {
637 printf (", \"content\": [{");
638 printf ("\"headers\": ");
639 format_headers_json (local, GMIME_MESSAGE (node->part), FALSE);
641 printf (", \"body\": [");
647 for (i = 0; i < node->nchildren; i++)
648 format_part_json (ctx, mime_node_child (node, i), i == 0);
650 printf ("%s}", terminator);
653 static notmuch_status_t
654 format_part_json_entry (const void *ctx, mime_node_t *node, unused (int indent),
655 unused (const notmuch_show_params_t *params))
657 format_part_json (ctx, node, TRUE);
659 return NOTMUCH_STATUS_SUCCESS;
662 /* Print a message in "mboxrd" format as documented, for example,
665 * http://qmail.org/qmail-manual-html/man5/mbox.html
667 static notmuch_status_t
668 format_part_mbox (const void *ctx, mime_node_t *node, unused (int indent),
669 unused (const notmuch_show_params_t *params))
671 notmuch_message_t *message = node->envelope_file;
673 const char *filename;
678 struct tm date_gmtime;
679 char date_asctime[26];
686 INTERNAL_ERROR ("format_part_mbox requires a root part");
688 filename = notmuch_message_get_filename (message);
689 file = fopen (filename, "r");
691 fprintf (stderr, "Failed to open %s: %s\n",
692 filename, strerror (errno));
693 return NOTMUCH_STATUS_FILE_ERROR;
696 from = notmuch_message_get_header (message, "from");
697 from = _extract_email_address (ctx, from);
699 date = notmuch_message_get_date (message);
700 gmtime_r (&date, &date_gmtime);
701 asctime_r (&date_gmtime, date_asctime);
703 printf ("From %s %s", from, date_asctime);
705 while ((line_len = getline (&line, &line_size, file)) != -1 ) {
706 if (_is_from_line (line))
715 return NOTMUCH_STATUS_SUCCESS;
718 static notmuch_status_t
719 format_part_raw (unused (const void *ctx), mime_node_t *node,
721 unused (const notmuch_show_params_t *params))
723 if (node->envelope_file) {
724 /* Special case the entire message to avoid MIME parsing. */
725 const char *filename;
730 filename = notmuch_message_get_filename (node->envelope_file);
731 if (filename == NULL) {
732 fprintf (stderr, "Error: Cannot get message filename.\n");
733 return NOTMUCH_STATUS_FILE_ERROR;
736 file = fopen (filename, "r");
738 fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno));
739 return NOTMUCH_STATUS_FILE_ERROR;
742 while (!feof (file)) {
743 size = fread (buf, 1, sizeof (buf), file);
745 fprintf (stderr, "Error: Read failed from %s\n", filename);
747 return NOTMUCH_STATUS_FILE_ERROR;
750 if (fwrite (buf, size, 1, stdout) != 1) {
751 fprintf (stderr, "Error: Write failed\n");
753 return NOTMUCH_STATUS_FILE_ERROR;
758 return NOTMUCH_STATUS_SUCCESS;
761 GMimeStream *stream_stdout;
762 GMimeStream *stream_filter = NULL;
764 stream_stdout = g_mime_stream_file_new (stdout);
765 g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
767 stream_filter = g_mime_stream_filter_new (stream_stdout);
769 if (GMIME_IS_PART (node->part)) {
770 /* For leaf parts, we emit only the transfer-decoded
772 GMimeDataWrapper *wrapper;
773 wrapper = g_mime_part_get_content_object (GMIME_PART (node->part));
775 if (wrapper && stream_filter)
776 g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
778 /* Write out the whole part. For message parts (the root
779 * part and embedded message parts), this will be the
780 * message including its headers (but not the
781 * encapsulating part's headers). For multipart parts,
782 * this will include the headers. */
784 g_mime_object_write_to_stream (node->part, stream_filter);
788 g_object_unref (stream_filter);
791 g_object_unref(stream_stdout);
793 return NOTMUCH_STATUS_SUCCESS;
796 static notmuch_status_t
797 show_message (void *ctx,
798 const notmuch_show_format_t *format,
799 notmuch_message_t *message,
801 notmuch_show_params_t *params)
804 void *local = talloc_new (ctx);
805 mime_node_t *root, *part;
806 notmuch_status_t status;
808 status = mime_node_open (local, message, params->cryptoctx,
809 params->decrypt, &root);
812 part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part));
814 status = format->part (local, part, indent, params);
820 if (params->part <= 0) {
821 fputs (format->message_start, stdout);
823 format->message(ctx, message, indent);
825 fputs (format->header_start, stdout);
827 format->header(ctx, message);
828 fputs (format->header_end, stdout);
830 fputs (format->body_start, stdout);
833 if (format->part_content)
834 show_message_body (message, format, params);
836 if (params->part <= 0) {
837 fputs (format->body_end, stdout);
839 fputs (format->message_end, stdout);
842 return NOTMUCH_STATUS_SUCCESS;
845 static notmuch_status_t
846 show_messages (void *ctx,
847 const notmuch_show_format_t *format,
848 notmuch_messages_t *messages,
850 notmuch_show_params_t *params)
852 notmuch_message_t *message;
853 notmuch_bool_t match;
856 notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
858 fputs (format->message_set_start, stdout);
861 notmuch_messages_valid (messages);
862 notmuch_messages_move_to_next (messages))
865 fputs (format->message_set_sep, stdout);
868 fputs (format->message_set_start, stdout);
870 message = notmuch_messages_get (messages);
872 match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
874 next_indent = indent;
876 if (match || params->entire_thread) {
877 status = show_message (ctx, format, message, indent, params);
880 next_indent = indent + 1;
883 fputs (format->message_set_sep, stdout);
886 status = show_messages (ctx,
888 notmuch_message_get_replies (message),
894 notmuch_message_destroy (message);
896 fputs (format->message_set_end, stdout);
899 fputs (format->message_set_end, stdout);
904 /* Formatted output of single message */
906 do_show_single (void *ctx,
907 notmuch_query_t *query,
908 const notmuch_show_format_t *format,
909 notmuch_show_params_t *params)
911 notmuch_messages_t *messages;
912 notmuch_message_t *message;
914 if (notmuch_query_count_messages (query) != 1) {
915 fprintf (stderr, "Error: search term did not match precisely one message.\n");
919 messages = notmuch_query_search_messages (query);
920 message = notmuch_messages_get (messages);
922 if (message == NULL) {
923 fprintf (stderr, "Error: Cannot find matching message.\n");
927 notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1);
929 return show_message (ctx, format, message, 0, params) != NOTMUCH_STATUS_SUCCESS;
932 /* Formatted output of threads */
935 notmuch_query_t *query,
936 const notmuch_show_format_t *format,
937 notmuch_show_params_t *params)
939 notmuch_threads_t *threads;
940 notmuch_thread_t *thread;
941 notmuch_messages_t *messages;
942 int first_toplevel = 1;
943 notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
945 fputs (format->message_set_start, stdout);
947 for (threads = notmuch_query_search_threads (query);
948 notmuch_threads_valid (threads);
949 notmuch_threads_move_to_next (threads))
951 thread = notmuch_threads_get (threads);
953 messages = notmuch_thread_get_toplevel_messages (thread);
955 if (messages == NULL)
956 INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
957 notmuch_thread_get_thread_id (thread));
960 fputs (format->message_set_sep, stdout);
963 status = show_messages (ctx, format, messages, 0, params);
967 notmuch_thread_destroy (thread);
971 fputs (format->message_set_end, stdout);
973 return res != NOTMUCH_STATUS_SUCCESS;
977 NOTMUCH_FORMAT_NOT_SPECIFIED,
985 notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
987 notmuch_config_t *config;
988 notmuch_database_t *notmuch;
989 notmuch_query_t *query;
992 const notmuch_show_format_t *format = &format_text;
993 notmuch_show_params_t params = { .part = -1 };
994 int format_sel = NOTMUCH_FORMAT_NOT_SPECIFIED;
995 notmuch_bool_t verify = FALSE;
996 notmuch_bool_t no_exclude = FALSE;
998 notmuch_opt_desc_t options[] = {
999 { NOTMUCH_OPT_KEYWORD, &format_sel, "format", 'f',
1000 (notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
1001 { "text", NOTMUCH_FORMAT_TEXT },
1002 { "mbox", NOTMUCH_FORMAT_MBOX },
1003 { "raw", NOTMUCH_FORMAT_RAW },
1005 { NOTMUCH_OPT_INT, ¶ms.part, "part", 'p', 0 },
1006 { NOTMUCH_OPT_BOOLEAN, ¶ms.entire_thread, "entire-thread", 't', 0 },
1007 { NOTMUCH_OPT_BOOLEAN, ¶ms.decrypt, "decrypt", 'd', 0 },
1008 { NOTMUCH_OPT_BOOLEAN, &verify, "verify", 'v', 0 },
1009 { NOTMUCH_OPT_BOOLEAN, &no_exclude, "no-exclude", 'n', 0 },
1013 opt_index = parse_arguments (argc, argv, options, 1);
1014 if (opt_index < 0) {
1015 /* diagnostics already printed */
1019 if (format_sel == NOTMUCH_FORMAT_NOT_SPECIFIED) {
1020 /* if part was requested and format was not specified, use format=raw */
1021 if (params.part >= 0)
1022 format_sel = NOTMUCH_FORMAT_RAW;
1024 format_sel = NOTMUCH_FORMAT_TEXT;
1027 switch (format_sel) {
1028 case NOTMUCH_FORMAT_JSON:
1029 format = &format_json;
1030 params.entire_thread = TRUE;
1032 case NOTMUCH_FORMAT_TEXT:
1033 format = &format_text;
1035 case NOTMUCH_FORMAT_MBOX:
1036 if (params.part > 0) {
1037 fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
1041 format = &format_mbox;
1043 case NOTMUCH_FORMAT_RAW:
1044 format = &format_raw;
1045 /* If --format=raw specified without specifying part, we can only
1046 * output single message, so set part=0 */
1047 if (params.part < 0)
1053 if (params.decrypt || verify) {
1054 #ifdef GMIME_ATLEAST_26
1055 /* TODO: GMimePasswordRequestFunc */
1056 params.cryptoctx = g_mime_gpg_context_new (NULL, "gpg");
1058 GMimeSession* session = g_object_new (g_mime_session_get_type(), NULL);
1059 params.cryptoctx = g_mime_gpg_context_new (session, "gpg");
1061 if (params.cryptoctx) {
1062 g_mime_gpg_context_set_always_trust ((GMimeGpgContext*) params.cryptoctx, FALSE);
1064 params.decrypt = FALSE;
1065 fprintf (stderr, "Failed to construct gpg context.\n");
1067 #ifndef GMIME_ATLEAST_26
1068 g_object_unref (session);
1072 config = notmuch_config_open (ctx, NULL, NULL);
1076 query_string = query_string_from_args (ctx, argc-opt_index, argv+opt_index);
1077 if (query_string == NULL) {
1078 fprintf (stderr, "Out of memory\n");
1082 if (*query_string == '\0') {
1083 fprintf (stderr, "Error: notmuch show requires at least one search term.\n");
1087 notmuch = notmuch_database_open (notmuch_config_get_database_path (config),
1088 NOTMUCH_DATABASE_MODE_READ_ONLY);
1089 if (notmuch == NULL)
1092 query = notmuch_query_create (notmuch, query_string);
1093 if (query == NULL) {
1094 fprintf (stderr, "Out of memory\n");
1098 /* if format=mbox then we can not output excluded messages as
1099 * there is no way to make the exclude flag available */
1100 if (format_sel == NOTMUCH_FORMAT_MBOX)
1101 notmuch_query_set_omit_excluded_messages (query, TRUE);
1103 /* If a single message is requested we do not use search_excludes. */
1104 if (params.part >= 0)
1105 ret = do_show_single (ctx, query, format, ¶ms);
1108 const char **search_exclude_tags;
1109 size_t search_exclude_tags_length;
1112 search_exclude_tags = notmuch_config_get_search_exclude_tags
1113 (config, &search_exclude_tags_length);
1114 for (i = 0; i < search_exclude_tags_length; i++)
1115 notmuch_query_add_tag_exclude (query, search_exclude_tags[i]);
1117 ret = do_show (ctx, query, format, ¶ms);
1121 notmuch_query_destroy (query);
1122 notmuch_database_close (notmuch);
1124 if (params.cryptoctx)
1125 g_object_unref(params.cryptoctx);