2009-11-10 21:03:05 +01:00
|
|
|
/* notmuch - Not much of an email program, (just index and search)
|
|
|
|
*
|
|
|
|
* Copyright © 2009 Carl Worth
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2016-06-02 18:26:14 +02:00
|
|
|
* along with this program. If not, see https://www.gnu.org/licenses/ .
|
2009-11-10 21:03:05 +01:00
|
|
|
*
|
|
|
|
* Author: Carl Worth <cworth@cworth.org>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "notmuch-client.h"
|
2012-03-27 23:59:50 +02:00
|
|
|
#include "gmime-filter-reply.h"
|
2012-08-03 03:14:50 +02:00
|
|
|
#include "sprinter.h"
|
2019-03-24 04:32:44 +01:00
|
|
|
#include "zlib-extra.h"
|
2009-11-10 21:03:05 +01:00
|
|
|
|
|
|
|
static const char *
|
2009-12-31 16:17:40 +01:00
|
|
|
_get_tags_as_string (const void *ctx, notmuch_message_t *message)
|
2009-11-10 21:03:05 +01:00
|
|
|
{
|
|
|
|
notmuch_tags_t *tags;
|
|
|
|
int first = 1;
|
|
|
|
const char *tag;
|
|
|
|
char *result;
|
|
|
|
|
|
|
|
result = talloc_strdup (ctx, "");
|
|
|
|
if (result == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (tags = notmuch_message_get_tags (message);
|
2010-03-09 18:22:29 +01:00
|
|
|
notmuch_tags_valid (tags);
|
2019-06-13 12:31:01 +02:00
|
|
|
notmuch_tags_move_to_next (tags)) {
|
2009-11-10 21:03:05 +01:00
|
|
|
tag = notmuch_tags_get (tags);
|
|
|
|
|
|
|
|
result = talloc_asprintf_append (result, "%s%s",
|
|
|
|
first ? "" : " ", tag);
|
|
|
|
first = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get a nice, single-line summary of message. */
|
|
|
|
static const char *
|
2009-12-31 16:17:40 +01:00
|
|
|
_get_one_line_summary (const void *ctx, notmuch_message_t *message)
|
2009-11-10 21:03:05 +01:00
|
|
|
{
|
|
|
|
const char *from;
|
|
|
|
time_t date;
|
|
|
|
const char *relative_date;
|
|
|
|
const char *tags;
|
|
|
|
|
|
|
|
from = notmuch_message_get_header (message, "from");
|
|
|
|
|
|
|
|
date = notmuch_message_get_date (message);
|
|
|
|
relative_date = notmuch_time_relative_date (ctx, date);
|
|
|
|
|
|
|
|
tags = _get_tags_as_string (ctx, message);
|
|
|
|
|
|
|
|
return talloc_asprintf (ctx, "%s (%s) (%s)",
|
|
|
|
from, relative_date, tags);
|
|
|
|
}
|
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
static const char *
|
|
|
|
_get_disposition (GMimeObject *meta)
|
2017-02-26 19:33:47 +01:00
|
|
|
{
|
|
|
|
GMimeContentDisposition *disposition;
|
|
|
|
|
|
|
|
disposition = g_mime_object_get_content_disposition (meta);
|
2019-06-13 12:31:01 +02:00
|
|
|
if (! disposition)
|
2017-02-26 19:33:47 +01:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
return g_mime_content_disposition_get_disposition (disposition);
|
|
|
|
}
|
|
|
|
|
2020-07-11 20:30:05 +02:00
|
|
|
static bool _get_message_flag (notmuch_message_t *message, notmuch_message_flag_t flag) {
|
|
|
|
notmuch_bool_t is_set;
|
|
|
|
notmuch_status_t status;
|
|
|
|
|
|
|
|
status = notmuch_message_get_flag_st (message, flag, &is_set);
|
|
|
|
|
|
|
|
if (print_status_message ("notmuch show", message, status))
|
|
|
|
INTERNAL_ERROR("unexpected error getting message flag\n");
|
|
|
|
|
|
|
|
return is_set;
|
|
|
|
}
|
|
|
|
|
2012-08-03 03:14:56 +02:00
|
|
|
/* Emit a sequence of key/value pairs for the metadata of message.
|
|
|
|
* The caller should begin a map before calling this. */
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
2012-12-06 22:12:12 +01:00
|
|
|
format_message_sprinter (sprinter_t *sp, notmuch_message_t *message)
|
2009-12-31 16:17:40 +01:00
|
|
|
{
|
2012-12-06 22:12:13 +01:00
|
|
|
/* Any changes to the JSON or S-Expression format should be
|
|
|
|
* reflected in the file devel/schemata. */
|
2012-11-07 16:27:01 +01:00
|
|
|
|
2012-08-03 03:14:56 +02:00
|
|
|
void *local = talloc_new (NULL);
|
2010-03-11 13:11:43 +01:00
|
|
|
notmuch_tags_t *tags;
|
2010-03-23 10:40:48 +01:00
|
|
|
time_t date;
|
|
|
|
const char *relative_date;
|
|
|
|
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->map_key (sp, "id");
|
|
|
|
sp->string (sp, notmuch_message_get_message_id (message));
|
|
|
|
|
|
|
|
sp->map_key (sp, "match");
|
2020-07-11 20:30:05 +02:00
|
|
|
sp->boolean (sp, _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH));
|
2012-08-03 03:14:56 +02:00
|
|
|
|
|
|
|
sp->map_key (sp, "excluded");
|
2020-07-11 20:30:05 +02:00
|
|
|
sp->boolean (sp, _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED));
|
2012-08-03 03:14:56 +02:00
|
|
|
|
|
|
|
sp->map_key (sp, "filename");
|
2017-02-25 14:31:31 +01:00
|
|
|
if (notmuch_format_version >= 3) {
|
|
|
|
notmuch_filenames_t *filenames;
|
|
|
|
|
|
|
|
sp->begin_list (sp);
|
|
|
|
for (filenames = notmuch_message_get_filenames (message);
|
|
|
|
notmuch_filenames_valid (filenames);
|
|
|
|
notmuch_filenames_move_to_next (filenames)) {
|
|
|
|
sp->string (sp, notmuch_filenames_get (filenames));
|
|
|
|
}
|
|
|
|
notmuch_filenames_destroy (filenames);
|
|
|
|
sp->end (sp);
|
|
|
|
} else {
|
|
|
|
sp->string (sp, notmuch_message_get_filename (message));
|
|
|
|
}
|
2012-08-03 03:14:56 +02:00
|
|
|
|
|
|
|
sp->map_key (sp, "timestamp");
|
2010-03-23 10:40:48 +01:00
|
|
|
date = notmuch_message_get_date (message);
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->integer (sp, date);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->map_key (sp, "date_relative");
|
|
|
|
relative_date = notmuch_time_relative_date (local, date);
|
|
|
|
sp->string (sp, relative_date);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->map_key (sp, "tags");
|
|
|
|
sp->begin_list (sp);
|
2010-03-11 13:11:43 +01:00
|
|
|
for (tags = notmuch_message_get_tags (message);
|
|
|
|
notmuch_tags_valid (tags);
|
|
|
|
notmuch_tags_move_to_next (tags))
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->string (sp, notmuch_tags_get (tags));
|
|
|
|
sp->end (sp);
|
|
|
|
|
|
|
|
talloc_free (local);
|
2009-12-31 16:17:40 +01:00
|
|
|
}
|
|
|
|
|
2010-06-09 04:45:54 +02:00
|
|
|
/* Extract just the email address from the contents of a From:
|
|
|
|
* header. */
|
|
|
|
static const char *
|
|
|
|
_extract_email_address (const void *ctx, const char *from)
|
|
|
|
{
|
|
|
|
InternetAddressList *addresses;
|
|
|
|
InternetAddress *address;
|
|
|
|
InternetAddressMailbox *mailbox;
|
|
|
|
const char *email = "MAILER-DAEMON";
|
|
|
|
|
2019-05-02 15:19:45 +02:00
|
|
|
addresses = internet_address_list_parse (NULL, from);
|
2010-06-09 04:45:54 +02:00
|
|
|
|
|
|
|
/* Bail if there is no address here. */
|
|
|
|
if (addresses == NULL || internet_address_list_length (addresses) < 1)
|
|
|
|
goto DONE;
|
|
|
|
|
|
|
|
/* Otherwise, just use the first address. */
|
|
|
|
address = internet_address_list_get_address (addresses, 0);
|
|
|
|
|
|
|
|
/* The From header should never contain an address group rather
|
|
|
|
* than a mailbox. So bail if it does. */
|
|
|
|
if (! INTERNET_ADDRESS_IS_MAILBOX (address))
|
|
|
|
goto DONE;
|
|
|
|
|
|
|
|
mailbox = INTERNET_ADDRESS_MAILBOX (address);
|
|
|
|
email = internet_address_mailbox_get_addr (mailbox);
|
|
|
|
email = talloc_strdup (ctx, email);
|
|
|
|
|
|
|
|
DONE:
|
2011-12-10 11:18:54 +01:00
|
|
|
if (addresses)
|
|
|
|
g_object_unref (addresses);
|
|
|
|
|
2010-06-09 04:45:54 +02:00
|
|
|
return email;
|
2019-06-13 12:31:01 +02:00
|
|
|
}
|
2010-06-09 04:45:54 +02:00
|
|
|
|
|
|
|
/* Return 1 if 'line' is an mbox From_ line---that is, a line
|
|
|
|
* beginning with zero or more '>' characters followed by the
|
|
|
|
* characters 'F', 'r', 'o', 'm', and space.
|
|
|
|
*
|
|
|
|
* Any characters at all may appear after that in the line.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
_is_from_line (const char *line)
|
|
|
|
{
|
|
|
|
const char *s = line;
|
|
|
|
|
|
|
|
if (line == NULL)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
while (*s == '>')
|
|
|
|
s++;
|
|
|
|
|
|
|
|
if (STRNCMP_LITERAL (s, "From ") == 0)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-03-18 17:32:36 +01:00
|
|
|
void
|
2012-12-06 22:12:12 +01:00
|
|
|
format_headers_sprinter (sprinter_t *sp, GMimeMessage *message,
|
2019-05-27 00:15:58 +02:00
|
|
|
bool reply, const _notmuch_message_crypto_t *msg_crypto)
|
2011-06-06 02:29:28 +02:00
|
|
|
{
|
2012-12-06 22:12:13 +01:00
|
|
|
/* Any changes to the JSON or S-Expression format should be
|
|
|
|
* reflected in the file devel/schemata. */
|
2012-11-07 16:27:01 +01:00
|
|
|
|
2017-03-16 17:53:47 +01:00
|
|
|
char *recipients_string;
|
2012-10-30 11:12:46 +01:00
|
|
|
const char *reply_to_string;
|
2017-05-04 20:59:37 +02:00
|
|
|
void *local = talloc_new (sp);
|
2011-06-06 02:29:28 +02:00
|
|
|
|
2012-08-03 03:14:53 +02:00
|
|
|
sp->begin_map (sp);
|
|
|
|
|
|
|
|
sp->map_key (sp, "Subject");
|
2019-05-27 00:15:58 +02:00
|
|
|
if (msg_crypto && msg_crypto->payload_subject) {
|
|
|
|
sp->string (sp, msg_crypto->payload_subject);
|
|
|
|
} else
|
|
|
|
sp->string (sp, g_mime_message_get_subject (message));
|
2012-08-03 03:14:53 +02:00
|
|
|
|
|
|
|
sp->map_key (sp, "From");
|
2017-05-06 04:26:57 +02:00
|
|
|
sp->string (sp, g_mime_message_get_from_string (message));
|
2012-08-03 03:14:53 +02:00
|
|
|
|
2017-05-06 13:41:14 +02:00
|
|
|
recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_TO);
|
2012-08-03 03:14:53 +02:00
|
|
|
if (recipients_string) {
|
|
|
|
sp->map_key (sp, "To");
|
|
|
|
sp->string (sp, recipients_string);
|
2017-03-16 17:53:47 +01:00
|
|
|
g_free (recipients_string);
|
2012-08-03 03:14:53 +02:00
|
|
|
}
|
|
|
|
|
2017-05-06 13:41:14 +02:00
|
|
|
recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_CC);
|
2012-08-03 03:14:53 +02:00
|
|
|
if (recipients_string) {
|
|
|
|
sp->map_key (sp, "Cc");
|
|
|
|
sp->string (sp, recipients_string);
|
2017-03-16 17:53:47 +01:00
|
|
|
g_free (recipients_string);
|
2012-08-03 03:14:53 +02:00
|
|
|
}
|
2012-03-18 17:32:36 +01:00
|
|
|
|
2017-05-06 13:41:14 +02:00
|
|
|
recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_BCC);
|
2012-09-10 08:35:30 +02:00
|
|
|
if (recipients_string) {
|
|
|
|
sp->map_key (sp, "Bcc");
|
|
|
|
sp->string (sp, recipients_string);
|
2017-03-16 17:53:47 +01:00
|
|
|
g_free (recipients_string);
|
2012-09-10 08:35:30 +02:00
|
|
|
}
|
|
|
|
|
2017-05-04 20:59:37 +02:00
|
|
|
reply_to_string = g_mime_message_get_reply_to_string (local, message);
|
2012-10-30 11:12:46 +01:00
|
|
|
if (reply_to_string) {
|
|
|
|
sp->map_key (sp, "Reply-To");
|
|
|
|
sp->string (sp, reply_to_string);
|
|
|
|
}
|
|
|
|
|
2012-03-18 17:32:36 +01:00
|
|
|
if (reply) {
|
2012-08-03 03:14:53 +02:00
|
|
|
sp->map_key (sp, "In-reply-to");
|
|
|
|
sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to"));
|
2012-03-18 17:32:36 +01:00
|
|
|
|
2012-08-03 03:14:53 +02:00
|
|
|
sp->map_key (sp, "References");
|
|
|
|
sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "References"));
|
2012-03-18 17:32:36 +01:00
|
|
|
} else {
|
2012-08-03 03:14:53 +02:00
|
|
|
sp->map_key (sp, "Date");
|
2017-05-04 14:48:44 +02:00
|
|
|
sp->string (sp, g_mime_message_get_date_string (sp, message));
|
2012-03-18 17:32:36 +01:00
|
|
|
}
|
|
|
|
|
2012-08-03 03:14:53 +02:00
|
|
|
sp->end (sp);
|
2017-05-04 20:59:37 +02:00
|
|
|
talloc_free (local);
|
2011-06-06 02:29:28 +02:00
|
|
|
}
|
|
|
|
|
2011-06-01 01:13:21 +02:00
|
|
|
/* Write a MIME text part out to the given stream.
|
2012-03-27 23:59:50 +02:00
|
|
|
*
|
|
|
|
* If (flags & NOTMUCH_SHOW_TEXT_PART_REPLY), this prepends "> " to
|
|
|
|
* each output line.
|
2011-06-01 01:13:21 +02:00
|
|
|
*
|
|
|
|
* Both line-ending conversion (CRLF->LF) and charset conversion ( ->
|
|
|
|
* UTF-8) will be performed, so it is inappropriate to call this
|
|
|
|
* function with a non-text part. Doing so will trigger an internal
|
|
|
|
* error.
|
|
|
|
*/
|
2012-03-27 23:59:50 +02:00
|
|
|
void
|
|
|
|
show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
|
|
|
|
notmuch_show_text_part_flags flags)
|
2009-11-21 01:09:36 +01:00
|
|
|
{
|
2011-06-01 01:13:21 +02:00
|
|
|
GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
|
2009-11-21 01:09:36 +01:00
|
|
|
GMimeStream *stream_filter = NULL;
|
2017-03-18 18:46:42 +01:00
|
|
|
GMimeFilter *crlf_filter = NULL;
|
2018-08-07 14:48:22 +02:00
|
|
|
GMimeFilter *windows_filter = NULL;
|
2009-11-21 01:09:36 +01:00
|
|
|
GMimeDataWrapper *wrapper;
|
2009-11-22 09:30:47 +01:00
|
|
|
const char *charset;
|
|
|
|
|
2011-06-01 01:13:21 +02:00
|
|
|
if (! g_mime_content_type_is_type (content_type, "text", "*"))
|
|
|
|
INTERNAL_ERROR ("Illegal request to format non-text part (%s) as text.",
|
2019-05-02 15:19:44 +02:00
|
|
|
g_mime_content_type_get_mime_type (content_type));
|
2011-06-01 01:13:21 +02:00
|
|
|
|
2011-06-01 01:18:45 +02:00
|
|
|
if (stream_out == NULL)
|
|
|
|
return;
|
2009-11-21 01:09:36 +01:00
|
|
|
|
2018-08-07 14:48:22 +02:00
|
|
|
charset = g_mime_object_get_content_type_parameter (part, "charset");
|
|
|
|
charset = charset ? g_mime_charset_canon_name (charset) : NULL;
|
2019-05-02 15:19:44 +02:00
|
|
|
wrapper = g_mime_part_get_content (GMIME_PART (part));
|
2019-06-13 12:31:01 +02:00
|
|
|
if (wrapper && charset && ! g_ascii_strncasecmp (charset, "iso-8859-", 9)) {
|
2018-08-07 14:48:22 +02:00
|
|
|
GMimeStream *null_stream = NULL;
|
|
|
|
GMimeStream *null_stream_filter = NULL;
|
|
|
|
|
|
|
|
/* Check for mislabeled Windows encoding */
|
|
|
|
null_stream = g_mime_stream_null_new ();
|
|
|
|
null_stream_filter = g_mime_stream_filter_new (null_stream);
|
|
|
|
windows_filter = g_mime_filter_windows_new (charset);
|
2019-06-13 12:31:01 +02:00
|
|
|
g_mime_stream_filter_add (GMIME_STREAM_FILTER (null_stream_filter),
|
|
|
|
windows_filter);
|
2018-08-07 14:48:22 +02:00
|
|
|
g_mime_data_wrapper_write_to_stream (wrapper, null_stream_filter);
|
2019-06-13 12:31:01 +02:00
|
|
|
charset = g_mime_filter_windows_real_charset (
|
2018-08-07 14:48:22 +02:00
|
|
|
(GMimeFilterWindows *) windows_filter);
|
|
|
|
|
|
|
|
if (null_stream_filter)
|
|
|
|
g_object_unref (null_stream_filter);
|
|
|
|
if (null_stream)
|
|
|
|
g_object_unref (null_stream);
|
|
|
|
/* Keep a reference to windows_filter in order to prevent the
|
|
|
|
* charset string from deallocation. */
|
|
|
|
}
|
|
|
|
|
2011-06-01 01:18:45 +02:00
|
|
|
stream_filter = g_mime_stream_filter_new (stream_out);
|
2019-05-02 15:19:44 +02:00
|
|
|
crlf_filter = g_mime_filter_dos2unix_new (false);
|
2019-06-13 12:31:01 +02:00
|
|
|
g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
|
|
|
|
crlf_filter);
|
2017-03-18 18:46:42 +01:00
|
|
|
g_object_unref (crlf_filter);
|
2011-06-01 01:18:45 +02:00
|
|
|
|
|
|
|
if (charset) {
|
|
|
|
GMimeFilter *charset_filter;
|
|
|
|
charset_filter = g_mime_filter_charset_new (charset, "UTF-8");
|
|
|
|
/* This result can be NULL for things like "unknown-8bit".
|
|
|
|
* Don't set a NULL filter as that makes GMime print
|
|
|
|
* annoying assertion-failure messages on stderr. */
|
2011-12-13 19:18:48 +01:00
|
|
|
if (charset_filter) {
|
2011-06-01 01:18:45 +02:00
|
|
|
g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
|
|
|
|
charset_filter);
|
2011-12-13 19:18:48 +01:00
|
|
|
g_object_unref (charset_filter);
|
|
|
|
}
|
|
|
|
|
2009-11-21 01:09:36 +01:00
|
|
|
}
|
|
|
|
|
2012-03-27 23:59:50 +02:00
|
|
|
if (flags & NOTMUCH_SHOW_TEXT_PART_REPLY) {
|
|
|
|
GMimeFilter *reply_filter;
|
2017-10-07 10:44:04 +02:00
|
|
|
reply_filter = g_mime_filter_reply_new (true);
|
2012-03-27 23:59:50 +02:00
|
|
|
if (reply_filter) {
|
|
|
|
g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
|
|
|
|
reply_filter);
|
|
|
|
g_object_unref (reply_filter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-11-21 01:09:36 +01:00
|
|
|
if (wrapper && stream_filter)
|
|
|
|
g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
|
|
|
|
if (stream_filter)
|
2019-06-13 12:31:01 +02:00
|
|
|
g_object_unref (stream_filter);
|
2018-08-07 14:48:22 +02:00
|
|
|
if (windows_filter)
|
|
|
|
g_object_unref (windows_filter);
|
2009-11-21 01:09:36 +01:00
|
|
|
}
|
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
static const char *
|
2017-05-14 19:49:31 +02:00
|
|
|
signature_status_to_string (GMimeSignatureStatus status)
|
2012-01-20 10:39:24 +01:00
|
|
|
{
|
2017-05-14 19:49:31 +02:00
|
|
|
if (g_mime_signature_status_bad (status))
|
2012-01-20 10:39:24 +01:00
|
|
|
return "bad";
|
2017-05-14 19:49:31 +02:00
|
|
|
|
|
|
|
if (g_mime_signature_status_error (status))
|
2012-01-20 10:39:24 +01:00
|
|
|
return "error";
|
2017-05-14 19:49:31 +02:00
|
|
|
|
|
|
|
if (g_mime_signature_status_good (status))
|
|
|
|
return "good";
|
|
|
|
|
2012-01-20 10:39:24 +01:00
|
|
|
return "unknown";
|
|
|
|
}
|
2011-05-26 03:01:17 +02:00
|
|
|
|
2017-06-03 19:47:34 +02:00
|
|
|
/* Print signature flags */
|
|
|
|
struct key_map_struct {
|
2019-05-02 15:19:43 +02:00
|
|
|
GMimeSignatureStatus bit;
|
2019-06-13 12:31:01 +02:00
|
|
|
const char *string;
|
2017-06-03 19:47:34 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
do_format_signature_errors (sprinter_t *sp, struct key_map_struct *key_map,
|
2019-06-13 12:31:01 +02:00
|
|
|
unsigned int array_map_len, GMimeSignatureStatus errors)
|
|
|
|
{
|
2017-06-03 19:47:34 +02:00
|
|
|
sp->map_key (sp, "errors");
|
|
|
|
sp->begin_map (sp);
|
|
|
|
|
|
|
|
for (unsigned int i = 0; i < array_map_len; i++) {
|
|
|
|
if (errors & key_map[i].bit) {
|
|
|
|
sp->map_key (sp, key_map[i].string);
|
2017-10-07 10:44:04 +02:00
|
|
|
sp->boolean (sp, true);
|
2017-06-03 19:47:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sp->end (sp);
|
|
|
|
}
|
|
|
|
|
2017-05-14 19:49:31 +02:00
|
|
|
static void
|
|
|
|
format_signature_errors (sprinter_t *sp, GMimeSignature *signature)
|
|
|
|
{
|
2019-05-02 15:19:43 +02:00
|
|
|
GMimeSignatureStatus errors = g_mime_signature_get_status (signature);
|
2017-05-14 19:49:31 +02:00
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
if (! (errors & GMIME_SIGNATURE_STATUS_ERROR_MASK))
|
2017-05-14 19:49:31 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
struct key_map_struct key_map[] = {
|
2019-06-13 12:31:01 +02:00
|
|
|
{ GMIME_SIGNATURE_STATUS_KEY_REVOKED, "key-revoked" },
|
|
|
|
{ GMIME_SIGNATURE_STATUS_KEY_EXPIRED, "key-expired" },
|
2017-05-14 19:49:31 +02:00
|
|
|
{ GMIME_SIGNATURE_STATUS_SIG_EXPIRED, "sig-expired" },
|
2019-06-13 12:31:01 +02:00
|
|
|
{ GMIME_SIGNATURE_STATUS_KEY_MISSING, "key-missing" },
|
|
|
|
{ GMIME_SIGNATURE_STATUS_CRL_MISSING, "crl-missing" },
|
|
|
|
{ GMIME_SIGNATURE_STATUS_CRL_TOO_OLD, "crl-too-old" },
|
|
|
|
{ GMIME_SIGNATURE_STATUS_BAD_POLICY, "bad-policy" },
|
|
|
|
{ GMIME_SIGNATURE_STATUS_SYS_ERROR, "sys-error" },
|
|
|
|
{ GMIME_SIGNATURE_STATUS_TOFU_CONFLICT, "tofu-conflict" },
|
2017-05-14 19:49:31 +02:00
|
|
|
};
|
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
do_format_signature_errors (sp, key_map, ARRAY_SIZE (key_map), errors);
|
2017-05-14 19:49:31 +02:00
|
|
|
}
|
2017-06-03 19:47:34 +02:00
|
|
|
|
2019-05-02 15:19:33 +02:00
|
|
|
/* Signature status sprinter */
|
2012-01-20 10:39:24 +01:00
|
|
|
static void
|
2019-04-20 19:51:39 +02:00
|
|
|
format_part_sigstatus_sprinter (sprinter_t *sp, GMimeSignatureList *siglist)
|
2012-01-20 10:39:24 +01:00
|
|
|
{
|
2012-12-06 22:12:13 +01:00
|
|
|
/* Any changes to the JSON or S-Expression format should be
|
|
|
|
* reflected in the file devel/schemata. */
|
2012-11-07 16:27:01 +01:00
|
|
|
|
2012-08-03 03:14:54 +02:00
|
|
|
sp->begin_list (sp);
|
2012-01-20 10:39:24 +01:00
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
if (! siglist) {
|
2012-08-03 03:14:54 +02:00
|
|
|
sp->end (sp);
|
2012-01-20 10:39:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < g_mime_signature_list_length (siglist); i++) {
|
|
|
|
GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i);
|
|
|
|
|
2012-08-03 03:14:54 +02:00
|
|
|
sp->begin_map (sp);
|
2012-01-20 10:39:24 +01:00
|
|
|
|
|
|
|
/* status */
|
|
|
|
GMimeSignatureStatus status = g_mime_signature_get_status (signature);
|
2012-08-03 03:14:54 +02:00
|
|
|
sp->map_key (sp, "status");
|
|
|
|
sp->string (sp, signature_status_to_string (status));
|
2012-01-20 10:39:24 +01:00
|
|
|
|
|
|
|
GMimeCertificate *certificate = g_mime_signature_get_certificate (signature);
|
2017-05-14 19:49:31 +02:00
|
|
|
if (g_mime_signature_status_good (status)) {
|
2012-08-03 03:14:54 +02:00
|
|
|
if (certificate) {
|
|
|
|
sp->map_key (sp, "fingerprint");
|
|
|
|
sp->string (sp, g_mime_certificate_get_fingerprint (certificate));
|
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
/* these dates are seconds since the epoch; should we
|
|
|
|
* provide a more human-readable format string? */
|
|
|
|
time_t created = g_mime_signature_get_created (signature);
|
2012-08-03 03:14:54 +02:00
|
|
|
if (created != -1) {
|
|
|
|
sp->map_key (sp, "created");
|
|
|
|
sp->integer (sp, created);
|
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
time_t expires = g_mime_signature_get_expires (signature);
|
2012-08-03 03:14:54 +02:00
|
|
|
if (expires > 0) {
|
|
|
|
sp->map_key (sp, "expires");
|
|
|
|
sp->integer (sp, expires);
|
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
if (certificate) {
|
2017-12-08 21:09:46 +01:00
|
|
|
const char *uid = g_mime_certificate_get_valid_userid (certificate);
|
|
|
|
if (uid) {
|
2012-08-03 03:14:54 +02:00
|
|
|
sp->map_key (sp, "userid");
|
2017-12-08 21:09:46 +01:00
|
|
|
sp->string (sp, uid);
|
2012-08-03 03:14:54 +02:00
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
}
|
|
|
|
} else if (certificate) {
|
2017-06-03 01:57:01 +02:00
|
|
|
const char *key_id = g_mime_certificate_get_fpr16 (certificate);
|
2012-08-03 03:14:54 +02:00
|
|
|
if (key_id) {
|
|
|
|
sp->map_key (sp, "keyid");
|
|
|
|
sp->string (sp, key_id);
|
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
}
|
|
|
|
|
2017-06-03 19:47:34 +02:00
|
|
|
if (notmuch_format_version <= 3) {
|
2019-05-02 15:19:43 +02:00
|
|
|
GMimeSignatureStatus errors = g_mime_signature_get_status (signature);
|
2017-05-14 19:49:31 +02:00
|
|
|
if (g_mime_signature_status_error (errors)) {
|
2017-06-03 19:47:34 +02:00
|
|
|
sp->map_key (sp, "errors");
|
|
|
|
sp->integer (sp, errors);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
format_signature_errors (sp, signature);
|
2012-01-20 10:39:24 +01:00
|
|
|
}
|
|
|
|
|
2012-08-03 03:14:54 +02:00
|
|
|
sp->end (sp);
|
2019-06-13 12:31:01 +02:00
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
|
2012-08-03 03:14:54 +02:00
|
|
|
sp->end (sp);
|
2012-01-20 10:39:24 +01:00
|
|
|
}
|
2013-03-30 14:53:17 +01:00
|
|
|
|
2012-03-06 19:48:39 +01:00
|
|
|
static notmuch_status_t
|
2012-08-03 03:14:52 +02:00
|
|
|
format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
|
2012-02-04 22:24:25 +01:00
|
|
|
int indent, const notmuch_show_params_t *params)
|
|
|
|
{
|
|
|
|
/* The disposition and content-type metadata are associated with
|
|
|
|
* the envelope for message parts */
|
2019-06-13 00:47:20 +02:00
|
|
|
GMimeObject *meta = node->envelope_part ? (
|
2019-07-03 06:31:19 +02:00
|
|
|
GMIME_OBJECT (node->envelope_part) ) : node->part;
|
2012-02-04 22:24:25 +01:00
|
|
|
GMimeContentType *content_type = g_mime_object_get_content_type (meta);
|
2017-10-07 10:44:04 +02:00
|
|
|
const bool leaf = GMIME_IS_PART (node->part);
|
2017-05-27 18:51:13 +02:00
|
|
|
GMimeStream *stream = params->out_stream;
|
2012-02-04 22:24:26 +01:00
|
|
|
const char *part_type;
|
2012-02-04 22:24:25 +01:00
|
|
|
int i;
|
|
|
|
|
|
|
|
if (node->envelope_file) {
|
|
|
|
notmuch_message_t *message = node->envelope_file;
|
2012-02-04 22:24:26 +01:00
|
|
|
|
|
|
|
part_type = "message";
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "\f%s{ id:%s depth:%d match:%d excluded:%d filename:%s\n",
|
|
|
|
part_type,
|
|
|
|
notmuch_message_get_message_id (message),
|
|
|
|
indent,
|
2020-07-11 20:30:05 +02:00
|
|
|
_get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0,
|
|
|
|
_get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0,
|
2017-05-27 18:51:13 +02:00
|
|
|
notmuch_message_get_filename (message));
|
2012-02-04 22:24:25 +01:00
|
|
|
} else {
|
2017-03-18 18:33:50 +01:00
|
|
|
char *content_string;
|
2017-02-26 19:33:47 +01:00
|
|
|
const char *disposition = _get_disposition (meta);
|
2012-02-04 22:24:25 +01:00
|
|
|
const char *cid = g_mime_object_get_content_id (meta);
|
2019-06-13 00:47:20 +02:00
|
|
|
const char *filename = leaf ? (
|
2019-07-03 06:31:19 +02:00
|
|
|
g_mime_part_get_filename (GMIME_PART (node->part)) ) : NULL;
|
2012-02-04 22:24:25 +01:00
|
|
|
|
|
|
|
if (disposition &&
|
2017-02-26 19:33:47 +01:00
|
|
|
strcasecmp (disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
|
2012-02-04 22:24:26 +01:00
|
|
|
part_type = "attachment";
|
|
|
|
else
|
|
|
|
part_type = "part";
|
2012-02-04 22:24:25 +01:00
|
|
|
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "\f%s{ ID: %d", part_type, node->part_num);
|
2012-02-04 22:24:26 +01:00
|
|
|
if (filename)
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, ", Filename: %s", filename);
|
2012-02-04 22:24:25 +01:00
|
|
|
if (cid)
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, ", Content-id: %s", cid);
|
2017-03-18 18:33:50 +01:00
|
|
|
|
2019-05-02 15:19:44 +02:00
|
|
|
content_string = g_mime_content_type_get_mime_type (content_type);
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, ", Content-type: %s\n", content_string);
|
2017-03-18 18:33:50 +01:00
|
|
|
g_free (content_string);
|
2012-02-04 22:24:25 +01:00
|
|
|
}
|
|
|
|
|
2012-02-04 22:24:26 +01:00
|
|
|
if (GMIME_IS_MESSAGE (node->part)) {
|
2012-02-04 22:24:25 +01:00
|
|
|
GMimeMessage *message = GMIME_MESSAGE (node->part);
|
2017-03-18 18:07:45 +01:00
|
|
|
char *recipients_string;
|
|
|
|
char *date_string;
|
2012-02-04 22:24:25 +01:00
|
|
|
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "\fheader{\n");
|
2012-02-04 22:24:26 +01:00
|
|
|
if (node->envelope_file)
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "%s\n", _get_one_line_summary (ctx, node->envelope_file));
|
|
|
|
g_mime_stream_printf (stream, "Subject: %s\n", g_mime_message_get_subject (message));
|
2017-05-06 04:26:57 +02:00
|
|
|
g_mime_stream_printf (stream, "From: %s\n", g_mime_message_get_from_string (message));
|
2017-05-06 13:41:14 +02:00
|
|
|
recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_TO);
|
2012-02-04 22:24:25 +01:00
|
|
|
if (recipients_string)
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "To: %s\n", recipients_string);
|
2017-03-18 18:07:45 +01:00
|
|
|
g_free (recipients_string);
|
2017-05-06 13:41:14 +02:00
|
|
|
recipients_string = g_mime_message_get_address_string (message, GMIME_ADDRESS_TYPE_CC);
|
2012-02-04 22:24:25 +01:00
|
|
|
if (recipients_string)
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "Cc: %s\n", recipients_string);
|
2017-03-18 18:07:45 +01:00
|
|
|
g_free (recipients_string);
|
2017-05-04 14:48:44 +02:00
|
|
|
date_string = g_mime_message_get_date_string (node, message);
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "Date: %s\n", date_string);
|
|
|
|
g_mime_stream_printf (stream, "\fheader}\n");
|
2012-02-04 22:24:26 +01:00
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
if (! params->output_body) {
|
2018-10-31 00:54:31 +01:00
|
|
|
g_mime_stream_printf (stream, "\f%s}\n", part_type);
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
|
|
|
}
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "\fbody{\n");
|
2012-02-04 22:24:25 +01:00
|
|
|
}
|
|
|
|
|
2012-02-04 22:24:26 +01:00
|
|
|
if (leaf) {
|
2012-02-04 22:24:25 +01:00
|
|
|
if (g_mime_content_type_is_type (content_type, "text", "*") &&
|
2018-10-31 00:54:32 +01:00
|
|
|
(params->include_html ||
|
2019-06-13 12:31:01 +02:00
|
|
|
! g_mime_content_type_is_type (content_type, "text", "html"))) {
|
2017-05-27 18:51:13 +02:00
|
|
|
show_text_part_content (node->part, stream, 0);
|
2012-02-04 22:24:26 +01:00
|
|
|
} else {
|
2019-05-02 15:19:44 +02:00
|
|
|
char *content_string = g_mime_content_type_get_mime_type (content_type);
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "Non-text part: %s\n", content_string);
|
2017-03-18 18:33:50 +01:00
|
|
|
g_free (content_string);
|
2012-02-04 22:24:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < node->nchildren; i++)
|
2012-08-03 03:14:52 +02:00
|
|
|
format_part_text (ctx, sp, mime_node_child (node, i), indent, params);
|
2012-02-04 22:24:25 +01:00
|
|
|
|
|
|
|
if (GMIME_IS_MESSAGE (node->part))
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "\fbody}\n");
|
2012-02-04 22:24:25 +01:00
|
|
|
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_printf (stream, "\f%s}\n", part_type);
|
2012-03-06 19:48:39 +01:00
|
|
|
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
2012-02-04 22:24:25 +01:00
|
|
|
}
|
|
|
|
|
2012-12-16 00:22:50 +01:00
|
|
|
static void
|
2012-12-16 00:24:23 +01:00
|
|
|
format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart *part)
|
2012-12-16 00:22:50 +01:00
|
|
|
{
|
|
|
|
const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset");
|
2012-12-16 00:24:23 +01:00
|
|
|
const char *cte = g_mime_object_get_header (meta, "content-transfer-encoding");
|
2019-05-02 15:19:44 +02:00
|
|
|
GMimeDataWrapper *wrapper = g_mime_part_get_content (part);
|
2012-12-16 00:24:23 +01:00
|
|
|
GMimeStream *stream = g_mime_data_wrapper_get_stream (wrapper);
|
|
|
|
ssize_t content_length = g_mime_stream_length (stream);
|
2012-12-16 00:22:50 +01:00
|
|
|
|
|
|
|
if (content_charset != NULL) {
|
|
|
|
sp->map_key (sp, "content-charset");
|
|
|
|
sp->string (sp, content_charset);
|
|
|
|
}
|
2012-12-16 00:24:23 +01:00
|
|
|
if (cte != NULL) {
|
|
|
|
sp->map_key (sp, "content-transfer-encoding");
|
|
|
|
sp->string (sp, cte);
|
|
|
|
}
|
|
|
|
if (content_length >= 0) {
|
|
|
|
sp->map_key (sp, "content-length");
|
|
|
|
sp->integer (sp, content_length);
|
|
|
|
}
|
2012-12-16 00:22:50 +01:00
|
|
|
}
|
|
|
|
|
2012-03-18 17:32:36 +01:00
|
|
|
void
|
2012-12-06 22:12:12 +01:00
|
|
|
format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
|
2017-10-07 10:44:04 +02:00
|
|
|
bool output_body,
|
|
|
|
bool include_html)
|
2012-02-20 01:26:24 +01:00
|
|
|
{
|
2012-12-06 22:12:13 +01:00
|
|
|
/* Any changes to the JSON or S-Expression format should be
|
|
|
|
* reflected in the file devel/schemata. */
|
2012-02-20 01:26:24 +01:00
|
|
|
|
|
|
|
if (node->envelope_file) {
|
2019-05-27 00:15:58 +02:00
|
|
|
const _notmuch_message_crypto_t *msg_crypto = NULL;
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->begin_map (sp);
|
2012-12-06 22:12:12 +01:00
|
|
|
format_message_sprinter (sp, node->envelope_file);
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2012-07-24 20:23:27 +02:00
|
|
|
if (output_body) {
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->map_key (sp, "body");
|
|
|
|
sp->begin_list (sp);
|
2017-10-07 10:44:04 +02:00
|
|
|
format_part_sprinter (ctx, sp, mime_node_child (node, 0), true, include_html);
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->end (sp);
|
2012-07-24 20:23:27 +02:00
|
|
|
}
|
2019-05-25 20:04:06 +02:00
|
|
|
|
2019-05-27 00:15:58 +02:00
|
|
|
msg_crypto = mime_node_get_message_crypto_status (node);
|
2019-05-25 20:04:06 +02:00
|
|
|
if (notmuch_format_version >= 4) {
|
|
|
|
sp->map_key (sp, "crypto");
|
|
|
|
sp->begin_map (sp);
|
|
|
|
if (msg_crypto->sig_list ||
|
|
|
|
msg_crypto->decryption_status != NOTMUCH_MESSAGE_DECRYPTED_NONE) {
|
|
|
|
if (msg_crypto->sig_list) {
|
|
|
|
sp->map_key (sp, "signed");
|
|
|
|
sp->begin_map (sp);
|
|
|
|
sp->map_key (sp, "status");
|
|
|
|
format_part_sigstatus_sprinter (sp, msg_crypto->sig_list);
|
|
|
|
if (msg_crypto->signature_encrypted) {
|
|
|
|
sp->map_key (sp, "encrypted");
|
|
|
|
sp->boolean (sp, msg_crypto->signature_encrypted);
|
|
|
|
}
|
2019-05-28 00:14:16 +02:00
|
|
|
if (msg_crypto->payload_subject) {
|
|
|
|
sp->map_key (sp, "headers");
|
|
|
|
sp->begin_list (sp);
|
|
|
|
sp->string (sp, "Subject");
|
|
|
|
sp->end (sp);
|
|
|
|
}
|
2019-05-25 20:04:06 +02:00
|
|
|
sp->end (sp);
|
|
|
|
}
|
|
|
|
if (msg_crypto->decryption_status != NOTMUCH_MESSAGE_DECRYPTED_NONE) {
|
|
|
|
sp->map_key (sp, "decrypted");
|
|
|
|
sp->begin_map (sp);
|
|
|
|
sp->map_key (sp, "status");
|
|
|
|
sp->string (sp, msg_crypto->decryption_status == NOTMUCH_MESSAGE_DECRYPTED_FULL ? "full" : "partial");
|
2019-05-28 00:14:16 +02:00
|
|
|
|
|
|
|
if (msg_crypto->payload_subject) {
|
|
|
|
const char *subject = g_mime_message_get_subject GMIME_MESSAGE (node->part);
|
|
|
|
if (subject == NULL || strcmp (subject, msg_crypto->payload_subject)) {
|
|
|
|
/* protected subject differs from the external header */
|
|
|
|
sp->map_key (sp, "header-mask");
|
|
|
|
sp->begin_map (sp);
|
|
|
|
sp->map_key (sp, "Subject");
|
|
|
|
if (subject == NULL)
|
|
|
|
sp->null (sp);
|
|
|
|
else
|
|
|
|
sp->string (sp, subject);
|
|
|
|
sp->end (sp);
|
|
|
|
}
|
|
|
|
}
|
2019-05-25 20:04:06 +02:00
|
|
|
sp->end (sp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sp->end (sp);
|
|
|
|
}
|
|
|
|
|
2019-05-27 00:15:54 +02:00
|
|
|
sp->map_key (sp, "headers");
|
2019-05-27 00:15:58 +02:00
|
|
|
format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false, msg_crypto);
|
2019-05-27 00:15:54 +02:00
|
|
|
|
2012-08-03 03:14:56 +02:00
|
|
|
sp->end (sp);
|
2012-02-20 01:26:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The disposition and content-type metadata are associated with
|
|
|
|
* the envelope for message parts */
|
2019-06-13 00:47:20 +02:00
|
|
|
GMimeObject *meta = node->envelope_part ? (
|
2019-07-03 06:31:19 +02:00
|
|
|
GMIME_OBJECT (node->envelope_part) ) : node->part;
|
2012-02-20 01:26:24 +01:00
|
|
|
GMimeContentType *content_type = g_mime_object_get_content_type (meta);
|
2017-03-18 18:33:50 +01:00
|
|
|
char *content_string;
|
2017-02-26 19:33:48 +01:00
|
|
|
const char *disposition = _get_disposition (meta);
|
2012-02-20 01:26:24 +01:00
|
|
|
const char *cid = g_mime_object_get_content_id (meta);
|
2019-06-13 00:47:20 +02:00
|
|
|
const char *filename = GMIME_IS_PART (node->part) ? (
|
|
|
|
g_mime_part_get_filename (GMIME_PART (node->part) ) ) : NULL;
|
2012-08-03 03:14:55 +02:00
|
|
|
int nclose = 0;
|
2012-02-20 01:26:24 +01:00
|
|
|
int i;
|
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->begin_map (sp);
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->map_key (sp, "id");
|
|
|
|
sp->integer (sp, node->part_num);
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
if (node->decrypt_attempted) {
|
|
|
|
sp->map_key (sp, "encstatus");
|
|
|
|
sp->begin_list (sp);
|
|
|
|
sp->begin_map (sp);
|
|
|
|
sp->map_key (sp, "status");
|
|
|
|
sp->string (sp, node->decrypt_success ? "good" : "bad");
|
|
|
|
sp->end (sp);
|
|
|
|
sp->end (sp);
|
|
|
|
}
|
2012-02-20 01:26:24 +01:00
|
|
|
|
|
|
|
if (node->verify_attempted) {
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->map_key (sp, "sigstatus");
|
2019-04-20 19:51:39 +02:00
|
|
|
format_part_sigstatus_sprinter (sp, node->sig_list);
|
2012-02-20 01:26:24 +01:00
|
|
|
}
|
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->map_key (sp, "content-type");
|
2019-05-02 15:19:44 +02:00
|
|
|
content_string = g_mime_content_type_get_mime_type (content_type);
|
2017-03-18 18:33:50 +01:00
|
|
|
sp->string (sp, content_string);
|
|
|
|
g_free (content_string);
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2017-02-26 19:33:48 +01:00
|
|
|
if (disposition) {
|
|
|
|
sp->map_key (sp, "content-disposition");
|
|
|
|
sp->string (sp, disposition);
|
|
|
|
}
|
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
if (cid) {
|
|
|
|
sp->map_key (sp, "content-id");
|
|
|
|
sp->string (sp, cid);
|
|
|
|
}
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
if (filename) {
|
|
|
|
sp->map_key (sp, "filename");
|
|
|
|
sp->string (sp, filename);
|
|
|
|
}
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2012-02-20 01:26:30 +01:00
|
|
|
if (GMIME_IS_PART (node->part)) {
|
2012-02-20 01:26:24 +01:00
|
|
|
/* For non-HTML text parts, we include the content in the
|
|
|
|
* JSON. Since JSON must be Unicode, we handle charset
|
|
|
|
* decoding here and do not report a charset to the caller.
|
2013-07-02 02:19:42 +02:00
|
|
|
* For text/html parts, we do not include the content unless
|
|
|
|
* the --include-html option has been passed. If a html part
|
|
|
|
* is not included, it can be requested directly. This makes
|
|
|
|
* charset decoding the responsibility on the caller so we
|
2012-02-20 01:26:24 +01:00
|
|
|
* report the charset for text/html parts.
|
|
|
|
*/
|
2012-12-16 00:22:50 +01:00
|
|
|
if (g_mime_content_type_is_type (content_type, "text", "*") &&
|
2013-07-02 02:19:42 +02:00
|
|
|
(include_html ||
|
2019-06-13 12:31:01 +02:00
|
|
|
! g_mime_content_type_is_type (content_type, "text", "html"))) {
|
2012-02-20 01:26:30 +01:00
|
|
|
GMimeStream *stream_memory = g_mime_stream_mem_new ();
|
|
|
|
GByteArray *part_content;
|
2012-03-27 23:59:50 +02:00
|
|
|
show_text_part_content (node->part, stream_memory, 0);
|
2012-02-20 01:26:24 +01:00
|
|
|
part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->map_key (sp, "content");
|
|
|
|
sp->string_len (sp, (char *) part_content->data, part_content->len);
|
2012-02-20 01:26:30 +01:00
|
|
|
g_object_unref (stream_memory);
|
2012-12-16 00:22:50 +01:00
|
|
|
} else {
|
2020-05-13 00:29:34 +02:00
|
|
|
/* if we have a child part despite being a standard
|
|
|
|
* (non-multipart) MIME part, that means there is
|
|
|
|
* something to unwrap, which we will present in
|
|
|
|
* content: */
|
|
|
|
if (node->nchildren) {
|
|
|
|
sp->map_key (sp, "content");
|
|
|
|
sp->begin_list (sp);
|
|
|
|
nclose = 1;
|
|
|
|
} else
|
|
|
|
format_omitted_part_meta_sprinter (sp, meta, GMIME_PART (node->part));
|
2012-02-20 01:26:24 +01:00
|
|
|
}
|
2012-02-20 01:26:30 +01:00
|
|
|
} else if (GMIME_IS_MULTIPART (node->part)) {
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->map_key (sp, "content");
|
|
|
|
sp->begin_list (sp);
|
|
|
|
nclose = 1;
|
2012-02-20 01:26:30 +01:00
|
|
|
} else if (GMIME_IS_MESSAGE (node->part)) {
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->map_key (sp, "content");
|
|
|
|
sp->begin_list (sp);
|
|
|
|
sp->begin_map (sp);
|
|
|
|
|
|
|
|
sp->map_key (sp, "headers");
|
2019-05-27 00:15:58 +02:00
|
|
|
format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false, NULL);
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
sp->map_key (sp, "body");
|
|
|
|
sp->begin_list (sp);
|
|
|
|
nclose = 3;
|
2012-02-20 01:26:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < node->nchildren; i++)
|
2017-10-07 10:44:04 +02:00
|
|
|
format_part_sprinter (ctx, sp, mime_node_child (node, i), true, include_html);
|
2012-02-20 01:26:24 +01:00
|
|
|
|
2012-08-03 03:14:55 +02:00
|
|
|
/* Close content structures */
|
|
|
|
for (i = 0; i < nclose; i++)
|
|
|
|
sp->end (sp);
|
|
|
|
/* Close part map */
|
|
|
|
sp->end (sp);
|
2012-02-20 01:26:24 +01:00
|
|
|
}
|
|
|
|
|
2012-03-06 19:48:39 +01:00
|
|
|
static notmuch_status_t
|
2012-12-06 22:12:12 +01:00
|
|
|
format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
|
|
|
|
mime_node_t *node, unused (int indent),
|
|
|
|
const notmuch_show_params_t *params)
|
2012-02-20 01:26:24 +01:00
|
|
|
{
|
2017-07-14 15:05:06 +02:00
|
|
|
format_part_sprinter (ctx, sp, node, params->output_body, params->include_html);
|
2012-03-06 19:48:39 +01:00
|
|
|
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
2012-02-20 01:26:24 +01:00
|
|
|
}
|
|
|
|
|
2012-03-06 19:48:40 +01:00
|
|
|
/* Print a message in "mboxrd" format as documented, for example,
|
|
|
|
* here:
|
|
|
|
*
|
|
|
|
* http://qmail.org/qmail-manual-html/man5/mbox.html
|
|
|
|
*/
|
2012-03-06 19:48:41 +01:00
|
|
|
static notmuch_status_t
|
2012-08-03 03:14:52 +02:00
|
|
|
format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
|
|
|
|
unused (int indent),
|
2012-03-06 19:48:41 +01:00
|
|
|
unused (const notmuch_show_params_t *params))
|
2012-03-06 19:48:40 +01:00
|
|
|
{
|
2012-03-06 19:48:41 +01:00
|
|
|
notmuch_message_t *message = node->envelope_file;
|
|
|
|
|
2012-03-06 19:48:40 +01:00
|
|
|
const char *filename;
|
2019-03-24 04:32:44 +01:00
|
|
|
gzFile file;
|
2012-03-06 19:48:40 +01:00
|
|
|
const char *from;
|
|
|
|
|
|
|
|
time_t date;
|
|
|
|
struct tm date_gmtime;
|
|
|
|
char date_asctime[26];
|
|
|
|
|
|
|
|
char *line = NULL;
|
2019-03-24 04:32:44 +01:00
|
|
|
ssize_t line_size;
|
2012-03-06 19:48:40 +01:00
|
|
|
ssize_t line_len;
|
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
if (! message)
|
2012-03-06 19:48:41 +01:00
|
|
|
INTERNAL_ERROR ("format_part_mbox requires a root part");
|
|
|
|
|
2012-03-06 19:48:40 +01:00
|
|
|
filename = notmuch_message_get_filename (message);
|
2019-03-24 04:32:44 +01:00
|
|
|
file = gzopen (filename, "r");
|
2012-03-06 19:48:40 +01:00
|
|
|
if (file == NULL) {
|
|
|
|
fprintf (stderr, "Failed to open %s: %s\n",
|
|
|
|
filename, strerror (errno));
|
2012-03-06 19:48:41 +01:00
|
|
|
return NOTMUCH_STATUS_FILE_ERROR;
|
2012-03-06 19:48:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
from = notmuch_message_get_header (message, "from");
|
|
|
|
from = _extract_email_address (ctx, from);
|
|
|
|
|
|
|
|
date = notmuch_message_get_date (message);
|
|
|
|
gmtime_r (&date, &date_gmtime);
|
|
|
|
asctime_r (&date_gmtime, date_asctime);
|
|
|
|
|
|
|
|
printf ("From %s %s", from, date_asctime);
|
|
|
|
|
2019-03-24 04:32:44 +01:00
|
|
|
while ((line_len = gz_getline (message, &line, &line_size, file)) != UTIL_EOF ) {
|
2012-03-06 19:48:40 +01:00
|
|
|
if (_is_from_line (line))
|
|
|
|
putchar ('>');
|
|
|
|
printf ("%s", line);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf ("\n");
|
|
|
|
|
2019-03-24 04:32:44 +01:00
|
|
|
gzclose (file);
|
2012-03-06 19:48:41 +01:00
|
|
|
|
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
2012-03-06 19:48:40 +01:00
|
|
|
}
|
|
|
|
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
static notmuch_status_t
|
2012-08-03 03:14:52 +02:00
|
|
|
format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
|
|
|
|
mime_node_t *node, unused (int indent),
|
2017-05-27 18:51:13 +02:00
|
|
|
const notmuch_show_params_t *params)
|
2012-03-06 19:48:42 +01:00
|
|
|
{
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
if (node->envelope_file) {
|
|
|
|
/* Special case the entire message to avoid MIME parsing. */
|
|
|
|
const char *filename;
|
2019-03-24 04:32:44 +01:00
|
|
|
GMimeStream *stream = NULL;
|
|
|
|
ssize_t ssize;
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
char buf[4096];
|
2019-03-24 04:32:44 +01:00
|
|
|
notmuch_status_t ret = NOTMUCH_STATUS_FILE_ERROR;
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
|
|
|
|
filename = notmuch_message_get_filename (node->envelope_file);
|
|
|
|
if (filename == NULL) {
|
|
|
|
fprintf (stderr, "Error: Cannot get message filename.\n");
|
2019-03-24 04:32:44 +01:00
|
|
|
goto DONE;
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
}
|
|
|
|
|
2019-03-24 04:32:44 +01:00
|
|
|
stream = g_mime_stream_gzfile_open (filename);
|
|
|
|
if (stream == NULL) {
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno));
|
2019-03-24 04:32:44 +01:00
|
|
|
goto DONE;
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
}
|
|
|
|
|
2019-03-24 04:32:44 +01:00
|
|
|
while (! g_mime_stream_eos (stream)) {
|
2019-06-13 12:31:01 +02:00
|
|
|
ssize = g_mime_stream_read (stream, buf, sizeof (buf));
|
2019-03-24 04:32:44 +01:00
|
|
|
if (ssize < 0) {
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
fprintf (stderr, "Error: Read failed from %s\n", filename);
|
2019-03-24 04:32:44 +01:00
|
|
|
goto DONE;
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
}
|
|
|
|
|
2019-03-24 04:32:44 +01:00
|
|
|
if (ssize > 0 && fwrite (buf, ssize, 1, stdout) != 1) {
|
2021-01-24 17:07:57 +01:00
|
|
|
fprintf (stderr, "Error: Write %zd chars to stdout failed\n", ssize);
|
2019-03-24 04:32:44 +01:00
|
|
|
goto DONE;
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-24 04:32:44 +01:00
|
|
|
ret = NOTMUCH_STATUS_SUCCESS;
|
|
|
|
|
|
|
|
/* XXX This DONE is just for the special case of a node in a single file */
|
2019-06-13 12:31:01 +02:00
|
|
|
DONE:
|
2019-03-24 04:32:44 +01:00
|
|
|
if (stream)
|
|
|
|
g_object_unref (stream);
|
|
|
|
|
|
|
|
return ret;
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
}
|
2012-03-06 19:48:42 +01:00
|
|
|
|
2017-05-27 18:51:13 +02:00
|
|
|
GMimeStream *stream_filter = g_mime_stream_filter_new (params->out_stream);
|
2012-03-06 19:48:42 +01:00
|
|
|
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
if (GMIME_IS_PART (node->part)) {
|
|
|
|
/* For leaf parts, we emit only the transfer-decoded
|
|
|
|
* body. */
|
|
|
|
GMimeDataWrapper *wrapper;
|
2019-05-02 15:19:44 +02:00
|
|
|
wrapper = g_mime_part_get_content (GMIME_PART (node->part));
|
2012-03-06 19:48:42 +01:00
|
|
|
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
if (wrapper && stream_filter)
|
|
|
|
g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
|
|
|
|
} else {
|
|
|
|
/* Write out the whole part. For message parts (the root
|
|
|
|
* part and embedded message parts), this will be the
|
|
|
|
* message including its headers (but not the
|
|
|
|
* encapsulating part's headers). For multipart parts,
|
|
|
|
* this will include the headers. */
|
|
|
|
if (stream_filter)
|
2019-05-02 15:19:45 +02:00
|
|
|
g_mime_object_write_to_stream (node->part, NULL, stream_filter);
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
}
|
2012-03-06 19:48:42 +01:00
|
|
|
|
|
|
|
if (stream_filter)
|
|
|
|
g_object_unref (stream_filter);
|
|
|
|
|
show: Convert raw format to the new self-recursive style, properly support interior parts
This is fully compatible for root and leaf parts, but now has proper
support for interior parts. This requires some design decisions that
were guided by what I would want if I were to save a part.
Specifically:
- Leaf parts are printed without headers and with transfer decoding.
This is what makes sense for saving attachments. (Furthermore, the
transfer decoding is necessary since, without the headers, the
caller would not be able to interpret non-transfer-decoded output.)
- Message parts are printed with their message headers, but without
enclosing part headers. This is what makes sense for saving a
message as a whole (which is a message part) and for saving attached
messages. This is symmetric for whole messages and for attached
messages, though we special-case the whole message for performance
reasons (and corner-case correctness reasons: given malformed input,
GMime may not be able to reproduce it from the parsed
representation).
- Multipart parts are printed with their headers and all child parts.
It's not clear what the best thing to do for multipart is, but this
was the most natural to implement and can be justified because such
parts can't be interpreted without their headers.
As an added benefit, we can move the special-case code for part 0 into
the raw formatter.
2012-03-06 19:48:43 +01:00
|
|
|
return NOTMUCH_STATUS_SUCCESS;
|
2012-03-06 19:48:42 +01:00
|
|
|
}
|
|
|
|
|
2012-03-06 19:48:39 +01:00
|
|
|
static notmuch_status_t
|
2011-05-20 20:45:33 +02:00
|
|
|
show_message (void *ctx,
|
|
|
|
const notmuch_show_format_t *format,
|
2012-08-03 03:14:52 +02:00
|
|
|
sprinter_t *sp,
|
2011-05-20 20:45:33 +02:00
|
|
|
notmuch_message_t *message,
|
2011-05-24 00:31:32 +02:00
|
|
|
int indent,
|
|
|
|
notmuch_show_params_t *params)
|
2009-12-31 16:17:40 +01:00
|
|
|
{
|
2012-04-08 02:57:46 +02:00
|
|
|
void *local = talloc_new (ctx);
|
|
|
|
mime_node_t *root, *part;
|
|
|
|
notmuch_status_t status;
|
2018-05-11 08:57:57 +02:00
|
|
|
unsigned int session_keys = 0;
|
|
|
|
notmuch_status_t session_key_count_error = NOTMUCH_STATUS_SUCCESS;
|
|
|
|
|
|
|
|
if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
|
|
|
|
session_key_count_error = notmuch_message_count_properties (message, "session-key", &session_keys);
|
2012-03-06 19:48:39 +01:00
|
|
|
|
2012-05-26 20:45:43 +02:00
|
|
|
status = mime_node_open (local, message, &(params->crypto), &root);
|
2012-04-08 02:57:46 +02:00
|
|
|
if (status)
|
|
|
|
goto DONE;
|
|
|
|
part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part));
|
|
|
|
if (part)
|
2012-08-03 03:14:52 +02:00
|
|
|
status = format->part (local, sp, part, indent, params);
|
2018-05-11 08:57:57 +02:00
|
|
|
if (params->crypto.decrypt == NOTMUCH_DECRYPT_TRUE && session_key_count_error == NOTMUCH_STATUS_SUCCESS) {
|
|
|
|
unsigned int new_session_keys = 0;
|
|
|
|
if (notmuch_message_count_properties (message, "session-key", &new_session_keys) == NOTMUCH_STATUS_SUCCESS &&
|
|
|
|
new_session_keys > session_keys) {
|
|
|
|
/* try a quiet re-indexing */
|
|
|
|
notmuch_indexopts_t *indexopts = notmuch_database_get_default_indexopts (notmuch_message_get_database (message));
|
|
|
|
if (indexopts) {
|
|
|
|
notmuch_indexopts_set_decrypt_policy (indexopts, NOTMUCH_DECRYPT_AUTO);
|
|
|
|
print_status_message ("Error re-indexing message with --decrypt=stash",
|
|
|
|
message, notmuch_message_reindex (message, indexopts));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-07-03 06:31:19 +02:00
|
|
|
DONE:
|
2012-04-08 02:57:46 +02:00
|
|
|
talloc_free (local);
|
|
|
|
return status;
|
2011-05-24 00:31:32 +02:00
|
|
|
}
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2012-03-06 19:48:39 +01:00
|
|
|
static notmuch_status_t
|
2011-05-20 20:45:33 +02:00
|
|
|
show_messages (void *ctx,
|
|
|
|
const notmuch_show_format_t *format,
|
2012-08-03 03:14:52 +02:00
|
|
|
sprinter_t *sp,
|
2011-05-20 20:45:33 +02:00
|
|
|
notmuch_messages_t *messages,
|
|
|
|
int indent,
|
2011-05-21 00:01:52 +02:00
|
|
|
notmuch_show_params_t *params)
|
2009-11-16 05:39:25 +01:00
|
|
|
{
|
|
|
|
notmuch_message_t *message;
|
2017-10-07 10:44:04 +02:00
|
|
|
bool match;
|
|
|
|
bool excluded;
|
2009-12-03 01:05:23 +01:00
|
|
|
int next_indent;
|
2012-03-06 19:48:39 +01:00
|
|
|
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2012-08-03 03:14:57 +02:00
|
|
|
sp->begin_list (sp);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2009-11-16 05:39:25 +01:00
|
|
|
for (;
|
2010-03-09 18:22:29 +01:00
|
|
|
notmuch_messages_valid (messages);
|
2019-06-13 12:31:01 +02:00
|
|
|
notmuch_messages_move_to_next (messages)) {
|
2012-08-03 03:14:57 +02:00
|
|
|
sp->begin_list (sp);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2009-11-16 05:39:25 +01:00
|
|
|
message = notmuch_messages_get (messages);
|
|
|
|
|
2020-07-11 20:30:05 +02:00
|
|
|
match = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
|
|
|
|
excluded = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED);
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2009-12-03 01:05:23 +01:00
|
|
|
next_indent = indent;
|
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
if ((match && (! excluded || ! params->omit_excluded)) || params->entire_thread) {
|
2012-08-03 03:14:52 +02:00
|
|
|
status = show_message (ctx, format, sp, message, indent, params);
|
2019-06-13 12:31:01 +02:00
|
|
|
if (status && ! res)
|
2012-03-06 19:48:39 +01:00
|
|
|
res = status;
|
2009-12-03 01:05:23 +01:00
|
|
|
next_indent = indent + 1;
|
2012-06-16 12:21:43 +02:00
|
|
|
} else {
|
2012-08-03 03:14:57 +02:00
|
|
|
sp->null (sp);
|
2009-12-03 01:05:23 +01:00
|
|
|
}
|
|
|
|
|
2012-03-06 19:48:39 +01:00
|
|
|
status = show_messages (ctx,
|
2012-08-03 03:14:52 +02:00
|
|
|
format, sp,
|
2012-03-06 19:48:39 +01:00
|
|
|
notmuch_message_get_replies (message),
|
|
|
|
next_indent,
|
|
|
|
params);
|
2019-06-13 12:31:01 +02:00
|
|
|
if (status && ! res)
|
2012-03-06 19:48:39 +01:00
|
|
|
res = status;
|
2009-11-16 05:39:25 +01:00
|
|
|
|
|
|
|
notmuch_message_destroy (message);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2012-08-03 03:14:57 +02:00
|
|
|
sp->end (sp);
|
2009-11-16 05:39:25 +01:00
|
|
|
}
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2012-08-03 03:14:57 +02:00
|
|
|
sp->end (sp);
|
2012-03-06 19:48:39 +01:00
|
|
|
|
|
|
|
return res;
|
2009-11-16 05:39:25 +01:00
|
|
|
}
|
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
/* Formatted output of single message */
|
2010-11-06 20:03:51 +01:00
|
|
|
static int
|
2011-05-23 05:04:02 +02:00
|
|
|
do_show_single (void *ctx,
|
|
|
|
notmuch_query_t *query,
|
|
|
|
const notmuch_show_format_t *format,
|
2012-08-03 03:14:52 +02:00
|
|
|
sprinter_t *sp,
|
2011-05-23 05:04:02 +02:00
|
|
|
notmuch_show_params_t *params)
|
2009-11-10 21:03:05 +01:00
|
|
|
{
|
|
|
|
notmuch_messages_t *messages;
|
2010-11-06 20:03:51 +01:00
|
|
|
notmuch_message_t *message;
|
2015-09-06 15:15:47 +02:00
|
|
|
notmuch_status_t status;
|
2015-09-27 17:31:58 +02:00
|
|
|
unsigned int count;
|
2009-11-28 03:49:39 +01:00
|
|
|
|
2017-02-26 22:21:34 +01:00
|
|
|
status = notmuch_query_count_messages (query, &count);
|
2015-09-27 17:31:58 +02:00
|
|
|
if (print_status_query ("notmuch show", query, status))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (count != 1) {
|
2017-06-17 00:50:25 +02:00
|
|
|
fprintf (stderr, "Error: search term did not match precisely one message (matched %u messages).\n", count);
|
2010-11-06 20:03:51 +01:00
|
|
|
return 1;
|
2009-11-28 03:49:39 +01:00
|
|
|
}
|
|
|
|
|
2017-02-26 22:21:32 +01:00
|
|
|
status = notmuch_query_search_messages (query, &messages);
|
2015-09-06 15:15:47 +02:00
|
|
|
if (print_status_query ("notmuch show", query, status))
|
|
|
|
return 1;
|
|
|
|
|
2010-11-06 20:03:51 +01:00
|
|
|
message = notmuch_messages_get (messages);
|
2009-11-10 21:03:05 +01:00
|
|
|
|
2010-11-06 20:03:51 +01:00
|
|
|
if (message == NULL) {
|
|
|
|
fprintf (stderr, "Error: Cannot find matching message.\n");
|
2009-11-12 05:29:30 +01:00
|
|
|
return 1;
|
2009-11-10 21:03:05 +01:00
|
|
|
}
|
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, 1);
|
|
|
|
|
2012-08-03 03:14:52 +02:00
|
|
|
return show_message (ctx, format, sp, message, 0, params)
|
2019-06-13 12:31:01 +02:00
|
|
|
!= NOTMUCH_STATUS_SUCCESS;
|
2010-11-06 20:03:51 +01:00
|
|
|
}
|
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
/* Formatted output of threads */
|
2010-11-06 20:03:51 +01:00
|
|
|
static int
|
2020-02-27 18:16:47 +01:00
|
|
|
do_show_threaded (void *ctx,
|
|
|
|
notmuch_query_t *query,
|
|
|
|
const notmuch_show_format_t *format,
|
|
|
|
sprinter_t *sp,
|
|
|
|
notmuch_show_params_t *params)
|
2010-11-06 20:03:51 +01:00
|
|
|
{
|
|
|
|
notmuch_threads_t *threads;
|
|
|
|
notmuch_thread_t *thread;
|
|
|
|
notmuch_messages_t *messages;
|
2012-03-06 19:48:39 +01:00
|
|
|
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
|
2010-11-06 20:03:51 +01:00
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
status = notmuch_query_search_threads (query, &threads);
|
2015-09-06 15:15:47 +02:00
|
|
|
if (print_status_query ("notmuch show", query, status))
|
2014-01-23 13:24:01 +01:00
|
|
|
return 1;
|
|
|
|
|
2012-08-03 03:14:58 +02:00
|
|
|
sp->begin_list (sp);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
for (;
|
2010-03-09 18:22:29 +01:00
|
|
|
notmuch_threads_valid (threads);
|
2019-06-13 12:31:01 +02:00
|
|
|
notmuch_threads_move_to_next (threads)) {
|
2009-12-03 01:05:23 +01:00
|
|
|
thread = notmuch_threads_get (threads);
|
2009-11-10 21:03:05 +01:00
|
|
|
|
2009-12-03 01:05:23 +01:00
|
|
|
messages = notmuch_thread_get_toplevel_messages (thread);
|
2009-11-18 02:53:54 +01:00
|
|
|
|
2009-12-03 01:05:23 +01:00
|
|
|
if (messages == NULL)
|
|
|
|
INTERNAL_ERROR ("Thread %s has no toplevel messages.\n",
|
|
|
|
notmuch_thread_get_thread_id (thread));
|
2009-11-28 03:49:39 +01:00
|
|
|
|
2012-08-03 03:14:52 +02:00
|
|
|
status = show_messages (ctx, format, sp, messages, 0, params);
|
2019-06-13 12:31:01 +02:00
|
|
|
if (status && ! res)
|
2012-03-06 19:48:39 +01:00
|
|
|
res = status;
|
2009-11-10 21:03:05 +01:00
|
|
|
|
2009-12-03 01:05:23 +01:00
|
|
|
notmuch_thread_destroy (thread);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2009-11-10 21:03:05 +01:00
|
|
|
}
|
|
|
|
|
2012-08-03 03:14:58 +02:00
|
|
|
sp->end (sp);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2012-03-06 19:48:39 +01:00
|
|
|
return res != NOTMUCH_STATUS_SUCCESS;
|
2009-11-10 21:03:05 +01:00
|
|
|
}
|
2010-03-24 08:21:20 +01:00
|
|
|
|
2020-02-27 18:16:47 +01:00
|
|
|
static int
|
|
|
|
do_show_unthreaded (void *ctx,
|
|
|
|
notmuch_query_t *query,
|
|
|
|
const notmuch_show_format_t *format,
|
|
|
|
sprinter_t *sp,
|
|
|
|
notmuch_show_params_t *params)
|
|
|
|
{
|
|
|
|
notmuch_messages_t *messages;
|
|
|
|
notmuch_message_t *message;
|
|
|
|
notmuch_status_t status, res = NOTMUCH_STATUS_SUCCESS;
|
|
|
|
notmuch_bool_t excluded;
|
|
|
|
|
|
|
|
status = notmuch_query_search_messages (query, &messages);
|
|
|
|
if (print_status_query ("notmuch show", query, status))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
sp->begin_list (sp);
|
|
|
|
|
|
|
|
for (;
|
|
|
|
notmuch_messages_valid (messages);
|
|
|
|
notmuch_messages_move_to_next (messages)) {
|
|
|
|
sp->begin_list (sp);
|
|
|
|
sp->begin_list (sp);
|
|
|
|
|
|
|
|
message = notmuch_messages_get (messages);
|
|
|
|
|
|
|
|
notmuch_message_set_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH, TRUE);
|
2020-07-11 20:30:05 +02:00
|
|
|
excluded = _get_message_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED);
|
2020-02-27 18:16:47 +01:00
|
|
|
|
|
|
|
if (!excluded || !params->omit_excluded) {
|
|
|
|
status = show_message (ctx, format, sp, message, 0, params);
|
|
|
|
if (status && !res)
|
|
|
|
res = status;
|
|
|
|
} else {
|
|
|
|
sp->null (sp);
|
|
|
|
}
|
|
|
|
notmuch_message_destroy (message);
|
|
|
|
sp->end (sp);
|
|
|
|
sp->end (sp);
|
|
|
|
}
|
|
|
|
sp->end (sp);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2012-02-06 20:57:22 +01:00
|
|
|
enum {
|
|
|
|
NOTMUCH_FORMAT_NOT_SPECIFIED,
|
|
|
|
NOTMUCH_FORMAT_JSON,
|
2012-12-06 22:12:13 +01:00
|
|
|
NOTMUCH_FORMAT_SEXP,
|
2012-02-06 20:57:22 +01:00
|
|
|
NOTMUCH_FORMAT_TEXT,
|
|
|
|
NOTMUCH_FORMAT_MBOX,
|
|
|
|
NOTMUCH_FORMAT_RAW
|
|
|
|
};
|
|
|
|
|
2017-01-06 21:14:48 +01:00
|
|
|
static const notmuch_show_format_t format_json = {
|
|
|
|
.new_sprinter = sprinter_json_create,
|
|
|
|
.part = format_part_sprinter_entry,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const notmuch_show_format_t format_sexp = {
|
|
|
|
.new_sprinter = sprinter_sexp_create,
|
|
|
|
.part = format_part_sprinter_entry,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const notmuch_show_format_t format_text = {
|
|
|
|
.new_sprinter = sprinter_text_create,
|
|
|
|
.part = format_part_text,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const notmuch_show_format_t format_mbox = {
|
|
|
|
.new_sprinter = sprinter_text_create,
|
|
|
|
.part = format_part_mbox,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const notmuch_show_format_t format_raw = {
|
|
|
|
.new_sprinter = sprinter_text_create,
|
|
|
|
.part = format_part_raw,
|
|
|
|
};
|
|
|
|
|
2017-01-06 21:14:45 +01:00
|
|
|
static const notmuch_show_format_t *formatters[] = {
|
|
|
|
[NOTMUCH_FORMAT_JSON] = &format_json,
|
|
|
|
[NOTMUCH_FORMAT_SEXP] = &format_sexp,
|
|
|
|
[NOTMUCH_FORMAT_TEXT] = &format_text,
|
|
|
|
[NOTMUCH_FORMAT_MBOX] = &format_mbox,
|
|
|
|
[NOTMUCH_FORMAT_RAW] = &format_raw,
|
|
|
|
};
|
|
|
|
|
2010-10-22 11:28:03 +02:00
|
|
|
int
|
2020-12-21 13:55:14 +01:00
|
|
|
notmuch_show_command (notmuch_config_t *config, unused(notmuch_database_t *notmuch), int argc, char *argv[])
|
2010-10-22 11:28:03 +02:00
|
|
|
{
|
|
|
|
notmuch_database_t *notmuch;
|
|
|
|
notmuch_query_t *query;
|
|
|
|
char *query_string;
|
2012-02-06 20:57:23 +01:00
|
|
|
int opt_index, ret;
|
2017-01-06 21:14:45 +01:00
|
|
|
const notmuch_show_format_t *formatter;
|
2012-08-03 03:14:50 +02:00
|
|
|
sprinter_t *sprinter;
|
2012-05-26 20:45:42 +02:00
|
|
|
notmuch_show_params_t params = {
|
|
|
|
.part = -1,
|
2017-10-07 10:44:04 +02:00
|
|
|
.omit_excluded = true,
|
|
|
|
.output_body = true,
|
2017-12-08 07:23:55 +01:00
|
|
|
.crypto = { .decrypt = NOTMUCH_DECRYPT_AUTO },
|
2012-05-26 20:45:42 +02:00
|
|
|
};
|
2017-01-06 21:14:46 +01:00
|
|
|
int format = NOTMUCH_FORMAT_NOT_SPECIFIED;
|
2017-10-07 10:44:04 +02:00
|
|
|
bool exclude = true;
|
|
|
|
bool entire_thread_set = false;
|
|
|
|
bool single_message;
|
2020-02-27 18:16:47 +01:00
|
|
|
bool unthreaded = FALSE;
|
2020-08-08 16:16:53 +02:00
|
|
|
char *status_string = NULL;
|
2012-02-06 20:57:22 +01:00
|
|
|
|
|
|
|
notmuch_opt_desc_t options[] = {
|
cli: use designated initializers for opt desc
Several changes at once, just to not have to change the same lines
several times over:
- Use designated initializers to initialize opt desc arrays.
- Only initialize the needed fields.
- Remove arg_id (short options) as unused.
- Replace opt_type and output_var with several type safe output
variables, where the output variable being non-NULL determines the
type. Introduce checks to ensure only one is set. The downside is
some waste of const space per argument; this could be saved by
retaining opt_type and using a union, but that's still pretty
verbose.
- Fix some variables due to the type safety. Mostly a good thing, but
leads to some enums being changed to ints. This is pedantically
correct, but somewhat annoying. We could also cast, but that defeats
the purpose a bit.
- Terminate the opt desc arrays using {}.
The output variable type safety and the ability to add new fields for
just some output types or arguments are the big wins. For example, if
we wanted to add a variable to set when the argument is present, we
could do so for just the arguments that need it.
Beauty is in the eye of the beholder, but I think this looks nice when
defining the arguments, and reduces some of the verbosity we have
there.
2017-10-01 22:53:11 +02:00
|
|
|
{ .opt_keyword = &format, .name = "format", .keywords =
|
2019-06-13 12:31:01 +02:00
|
|
|
(notmuch_keyword_t []){ { "json", NOTMUCH_FORMAT_JSON },
|
|
|
|
{ "text", NOTMUCH_FORMAT_TEXT },
|
|
|
|
{ "sexp", NOTMUCH_FORMAT_SEXP },
|
|
|
|
{ "mbox", NOTMUCH_FORMAT_MBOX },
|
|
|
|
{ "raw", NOTMUCH_FORMAT_RAW },
|
|
|
|
{ 0, 0 } } },
|
cli: use designated initializers for opt desc
Several changes at once, just to not have to change the same lines
several times over:
- Use designated initializers to initialize opt desc arrays.
- Only initialize the needed fields.
- Remove arg_id (short options) as unused.
- Replace opt_type and output_var with several type safe output
variables, where the output variable being non-NULL determines the
type. Introduce checks to ensure only one is set. The downside is
some waste of const space per argument; this could be saved by
retaining opt_type and using a union, but that's still pretty
verbose.
- Fix some variables due to the type safety. Mostly a good thing, but
leads to some enums being changed to ints. This is pedantically
correct, but somewhat annoying. We could also cast, but that defeats
the purpose a bit.
- Terminate the opt desc arrays using {}.
The output variable type safety and the ability to add new fields for
just some output types or arguments are the big wins. For example, if
we wanted to add a variable to set when the argument is present, we
could do so for just the arguments that need it.
Beauty is in the eye of the beholder, but I think this looks nice when
defining the arguments, and reduces some of the verbosity we have
there.
2017-10-01 22:53:11 +02:00
|
|
|
{ .opt_int = ¬much_format_version, .name = "format-version" },
|
|
|
|
{ .opt_bool = &exclude, .name = "exclude" },
|
2017-10-01 22:53:16 +02:00
|
|
|
{ .opt_bool = ¶ms.entire_thread, .name = "entire-thread",
|
|
|
|
.present = &entire_thread_set },
|
2020-02-27 18:16:47 +01:00
|
|
|
{ .opt_bool = &unthreaded, .name = "unthreaded" },
|
cli: use designated initializers for opt desc
Several changes at once, just to not have to change the same lines
several times over:
- Use designated initializers to initialize opt desc arrays.
- Only initialize the needed fields.
- Remove arg_id (short options) as unused.
- Replace opt_type and output_var with several type safe output
variables, where the output variable being non-NULL determines the
type. Introduce checks to ensure only one is set. The downside is
some waste of const space per argument; this could be saved by
retaining opt_type and using a union, but that's still pretty
verbose.
- Fix some variables due to the type safety. Mostly a good thing, but
leads to some enums being changed to ints. This is pedantically
correct, but somewhat annoying. We could also cast, but that defeats
the purpose a bit.
- Terminate the opt desc arrays using {}.
The output variable type safety and the ability to add new fields for
just some output types or arguments are the big wins. For example, if
we wanted to add a variable to set when the argument is present, we
could do so for just the arguments that need it.
Beauty is in the eye of the beholder, but I think this looks nice when
defining the arguments, and reduces some of the verbosity we have
there.
2017-10-01 22:53:11 +02:00
|
|
|
{ .opt_int = ¶ms.part, .name = "part" },
|
2019-06-13 12:31:01 +02:00
|
|
|
{ .opt_keyword = (int *) (¶ms.crypto.decrypt), .name = "decrypt",
|
2017-12-19 17:40:54 +01:00
|
|
|
.keyword_no_arg_value = "true", .keywords =
|
2019-06-13 12:31:01 +02:00
|
|
|
(notmuch_keyword_t []){ { "false", NOTMUCH_DECRYPT_FALSE },
|
|
|
|
{ "auto", NOTMUCH_DECRYPT_AUTO },
|
|
|
|
{ "true", NOTMUCH_DECRYPT_NOSTASH },
|
|
|
|
{ "stash", NOTMUCH_DECRYPT_TRUE },
|
|
|
|
{ 0, 0 } } },
|
cli: use designated initializers for opt desc
Several changes at once, just to not have to change the same lines
several times over:
- Use designated initializers to initialize opt desc arrays.
- Only initialize the needed fields.
- Remove arg_id (short options) as unused.
- Replace opt_type and output_var with several type safe output
variables, where the output variable being non-NULL determines the
type. Introduce checks to ensure only one is set. The downside is
some waste of const space per argument; this could be saved by
retaining opt_type and using a union, but that's still pretty
verbose.
- Fix some variables due to the type safety. Mostly a good thing, but
leads to some enums being changed to ints. This is pedantically
correct, but somewhat annoying. We could also cast, but that defeats
the purpose a bit.
- Terminate the opt desc arrays using {}.
The output variable type safety and the ability to add new fields for
just some output types or arguments are the big wins. For example, if
we wanted to add a variable to set when the argument is present, we
could do so for just the arguments that need it.
Beauty is in the eye of the beholder, but I think this looks nice when
defining the arguments, and reduces some of the verbosity we have
there.
2017-10-01 22:53:11 +02:00
|
|
|
{ .opt_bool = ¶ms.crypto.verify, .name = "verify" },
|
|
|
|
{ .opt_bool = ¶ms.output_body, .name = "body" },
|
|
|
|
{ .opt_bool = ¶ms.include_html, .name = "include-html" },
|
|
|
|
{ .opt_inherit = notmuch_shared_options },
|
|
|
|
{ }
|
2012-02-06 20:57:22 +01:00
|
|
|
};
|
2010-10-22 11:28:03 +02:00
|
|
|
|
2012-02-06 20:57:22 +01:00
|
|
|
opt_index = parse_arguments (argc, argv, options, 1);
|
2014-01-10 22:28:53 +01:00
|
|
|
if (opt_index < 0)
|
|
|
|
return EXIT_FAILURE;
|
2011-05-21 00:01:52 +02:00
|
|
|
|
2015-04-05 15:13:03 +02:00
|
|
|
notmuch_process_shared_options (argv[0]);
|
|
|
|
|
2017-12-19 17:40:54 +01:00
|
|
|
/* explicit decryption implies verification */
|
cli/show: enable --decrypt=stash
Add fancy new feature, which makes "notmuch show" capable of actually
indexing messages that it just decrypted.
This enables a workflow where messages can come in in the background
and be indexed using "--decrypt=auto". But when showing an encrypted
message for the first time, it gets automatically indexed.
This is something of a departure for "notmuch show" -- in particular,
because it requires read/write access to the database. However, this
might be a common use case -- people get mail delivered and indexed in
the background, but only want access to their secret key to happen
when they're directly interacting with notmuch itself.
In such a scenario, they couldn't search newly-delivered, encrypted
messages, but they could search for them once they've read them.
Documentation of this new feature also uses a table form, similar to
that found in the description of index.decrypt in notmuch-config(1).
A notmuch UI that wants to facilitate this workflow while also
offering an interactive search interface might instead make use of
these additional commands while the user is at the console:
Count received encrypted messages (if > 0, there are some things we
haven't yet tried to index, and therefore can't yet search):
notmuch count tag:encrypted and \
not property:index.decryption=success and \
not property:index.decryption=failure
Reindex those messages:
notmuch reindex --try-decrypt=true tag:encrypted and \
not property:index.decryption=success and \
not property:index.decryption=failure
2018-05-11 08:57:59 +02:00
|
|
|
if (params.crypto.decrypt == NOTMUCH_DECRYPT_NOSTASH ||
|
|
|
|
params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
|
2017-12-19 17:40:54 +01:00
|
|
|
params.crypto.verify = true;
|
2012-05-26 20:45:45 +02:00
|
|
|
|
2017-01-06 21:14:42 +01:00
|
|
|
/* specifying a part implies single message display */
|
|
|
|
single_message = params.part >= 0;
|
|
|
|
|
2017-01-06 21:14:46 +01:00
|
|
|
if (format == NOTMUCH_FORMAT_NOT_SPECIFIED) {
|
2012-02-06 20:57:22 +01:00
|
|
|
/* if part was requested and format was not specified, use format=raw */
|
|
|
|
if (params.part >= 0)
|
2017-01-06 21:14:46 +01:00
|
|
|
format = NOTMUCH_FORMAT_RAW;
|
2012-02-06 20:57:22 +01:00
|
|
|
else
|
2017-01-06 21:14:46 +01:00
|
|
|
format = NOTMUCH_FORMAT_TEXT;
|
2012-02-06 20:57:22 +01:00
|
|
|
}
|
2011-10-21 14:19:17 +02:00
|
|
|
|
2017-01-06 21:14:46 +01:00
|
|
|
if (format == NOTMUCH_FORMAT_MBOX) {
|
2012-02-06 20:57:22 +01:00
|
|
|
if (params.part > 0) {
|
|
|
|
fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
|
2014-01-10 22:28:53 +01:00
|
|
|
return EXIT_FAILURE;
|
2010-11-06 20:03:51 +01:00
|
|
|
}
|
2017-01-06 21:14:46 +01:00
|
|
|
} else if (format == NOTMUCH_FORMAT_RAW) {
|
2017-01-06 21:14:42 +01:00
|
|
|
/* raw format only supports single message display */
|
2017-10-07 10:44:04 +02:00
|
|
|
single_message = true;
|
2012-02-06 20:57:22 +01:00
|
|
|
}
|
|
|
|
|
2012-12-16 04:17:25 +01:00
|
|
|
notmuch_exit_if_unsupported_format ();
|
|
|
|
|
2017-10-07 10:44:04 +02:00
|
|
|
/* Default is entire-thread = false except for format=json and
|
2012-12-06 22:12:13 +01:00
|
|
|
* format=sexp. */
|
2017-10-01 22:53:16 +02:00
|
|
|
if (! entire_thread_set &&
|
|
|
|
(format == NOTMUCH_FORMAT_JSON || format == NOTMUCH_FORMAT_SEXP))
|
2017-10-07 10:44:04 +02:00
|
|
|
params.entire_thread = true;
|
2012-06-16 12:21:44 +02:00
|
|
|
|
2019-06-13 12:31:01 +02:00
|
|
|
if (! params.output_body) {
|
2012-07-24 20:23:27 +02:00
|
|
|
if (params.part > 0) {
|
|
|
|
fprintf (stderr, "Warning: --body=false is incompatible with --part > 0. Disabling.\n");
|
2017-10-07 10:44:04 +02:00
|
|
|
params.output_body = true;
|
2012-07-24 20:23:27 +02:00
|
|
|
} else {
|
2018-10-31 00:54:31 +01:00
|
|
|
if (format != NOTMUCH_FORMAT_TEXT &&
|
|
|
|
format != NOTMUCH_FORMAT_JSON &&
|
|
|
|
format != NOTMUCH_FORMAT_SEXP)
|
2012-12-06 22:12:13 +01:00
|
|
|
fprintf (stderr,
|
2018-10-31 00:54:31 +01:00
|
|
|
"Warning: --body=false only implemented for format=text, format=json and format=sexp\n");
|
2012-07-24 20:23:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-02 02:19:42 +02:00
|
|
|
if (params.include_html &&
|
2019-06-13 12:31:01 +02:00
|
|
|
(format != NOTMUCH_FORMAT_TEXT &&
|
2018-10-31 00:54:32 +01:00
|
|
|
format != NOTMUCH_FORMAT_JSON &&
|
|
|
|
format != NOTMUCH_FORMAT_SEXP)) {
|
|
|
|
fprintf (stderr, "Warning: --include-html only implemented for format=text, format=json and format=sexp\n");
|
2013-07-02 02:19:42 +02:00
|
|
|
}
|
|
|
|
|
2020-08-08 16:16:53 +02:00
|
|
|
notmuch_database_mode_t mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
|
|
|
|
if (params.crypto.decrypt == NOTMUCH_DECRYPT_TRUE)
|
|
|
|
mode = NOTMUCH_DATABASE_MODE_READ_WRITE;
|
|
|
|
if (notmuch_database_open_with_config (NULL,
|
|
|
|
mode,
|
|
|
|
_notmuch_config_get_path (config),
|
|
|
|
NULL,
|
|
|
|
¬much,
|
|
|
|
&status_string)) {
|
|
|
|
if (status_string) {
|
|
|
|
fputs (status_string, stderr);
|
|
|
|
free (status_string);
|
|
|
|
}
|
|
|
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
config = NULL;
|
|
|
|
|
|
|
|
notmuch_exit_if_unmatched_db_uuid (notmuch);
|
|
|
|
|
|
|
|
query_string = query_string_from_args (notmuch, argc - opt_index, argv + opt_index);
|
2010-10-22 11:28:03 +02:00
|
|
|
if (query_string == NULL) {
|
|
|
|
fprintf (stderr, "Out of memory\n");
|
2014-01-10 22:28:53 +01:00
|
|
|
return EXIT_FAILURE;
|
2010-10-22 11:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (*query_string == '\0') {
|
2010-11-06 20:03:51 +01:00
|
|
|
fprintf (stderr, "Error: notmuch show requires at least one search term.\n");
|
2014-01-10 22:28:53 +01:00
|
|
|
return EXIT_FAILURE;
|
2010-10-22 11:28:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
query = notmuch_query_create (notmuch, query_string);
|
|
|
|
if (query == NULL) {
|
2010-11-06 20:03:51 +01:00
|
|
|
fprintf (stderr, "Out of memory\n");
|
2014-01-10 22:28:53 +01:00
|
|
|
return EXIT_FAILURE;
|
2010-10-22 11:28:03 +02:00
|
|
|
}
|
|
|
|
|
2012-08-03 03:14:50 +02:00
|
|
|
/* Create structure printer. */
|
2017-01-06 21:14:46 +01:00
|
|
|
formatter = formatters[format];
|
2020-08-08 16:16:53 +02:00
|
|
|
sprinter = formatter->new_sprinter (notmuch, stdout);
|
2012-08-03 03:14:50 +02:00
|
|
|
|
2017-05-27 18:51:13 +02:00
|
|
|
params.out_stream = g_mime_stream_stdout_new ();
|
|
|
|
|
2012-03-01 23:30:40 +01:00
|
|
|
/* If a single message is requested we do not use search_excludes. */
|
2017-01-06 21:14:42 +01:00
|
|
|
if (single_message) {
|
2020-08-08 16:16:53 +02:00
|
|
|
ret = do_show_single (notmuch, query, formatter, sprinter, ¶ms);
|
2017-01-06 21:14:42 +01:00
|
|
|
} else {
|
2012-04-07 18:10:06 +02:00
|
|
|
/* We always apply set the exclude flag. The
|
|
|
|
* exclude=true|false option controls whether or not we return
|
|
|
|
* threads that only match in an excluded message */
|
2020-08-08 16:16:53 +02:00
|
|
|
notmuch_config_values_t *exclude_tags;
|
2017-02-18 16:08:04 +01:00
|
|
|
notmuch_status_t status;
|
2012-04-07 18:10:06 +02:00
|
|
|
|
2020-08-08 16:16:53 +02:00
|
|
|
for (exclude_tags = notmuch_config_get_values (notmuch, NOTMUCH_CONFIG_EXCLUDE_TAGS);
|
|
|
|
notmuch_config_values_valid (exclude_tags);
|
|
|
|
notmuch_config_values_move_to_next (exclude_tags)) {
|
2017-02-18 16:08:04 +01:00
|
|
|
|
2020-08-08 16:16:53 +02:00
|
|
|
status = notmuch_query_add_tag_exclude (query,
|
|
|
|
notmuch_config_values_get (exclude_tags));
|
2017-02-18 16:08:04 +01:00
|
|
|
if (status && status != NOTMUCH_STATUS_IGNORED) {
|
|
|
|
print_status_query ("notmuch show", query, status);
|
|
|
|
ret = -1;
|
|
|
|
goto DONE;
|
|
|
|
}
|
|
|
|
}
|
2012-04-07 18:10:06 +02:00
|
|
|
|
2017-10-07 10:44:04 +02:00
|
|
|
if (exclude == false) {
|
|
|
|
notmuch_query_set_omit_excluded (query, false);
|
|
|
|
params.omit_excluded = false;
|
2012-03-01 23:30:40 +01:00
|
|
|
}
|
2012-04-07 18:10:06 +02:00
|
|
|
|
2020-02-27 18:16:47 +01:00
|
|
|
if (unthreaded)
|
2020-08-08 16:16:53 +02:00
|
|
|
ret = do_show_unthreaded (notmuch, query, formatter, sprinter, ¶ms);
|
2020-02-27 18:16:47 +01:00
|
|
|
else
|
2020-08-08 16:16:53 +02:00
|
|
|
ret = do_show_threaded (notmuch, query, formatter, sprinter, ¶ms);
|
2012-03-01 23:30:40 +01:00
|
|
|
}
|
|
|
|
|
2019-07-03 06:31:19 +02:00
|
|
|
DONE:
|
2017-05-27 18:51:13 +02:00
|
|
|
g_mime_stream_flush (params.out_stream);
|
|
|
|
g_object_unref (params.out_stream);
|
|
|
|
|
2017-10-10 07:49:02 +02:00
|
|
|
_notmuch_crypto_cleanup (¶ms.crypto);
|
2010-10-22 11:28:03 +02:00
|
|
|
notmuch_query_destroy (query);
|
2012-04-22 14:07:53 +02:00
|
|
|
notmuch_database_destroy (notmuch);
|
2010-10-22 11:28:03 +02:00
|
|
|
|
2014-01-10 22:28:53 +01:00
|
|
|
return ret ? EXIT_FAILURE : EXIT_SUCCESS;
|
2010-10-22 11:28:03 +02:00
|
|
|
}
|