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
|
|
|
|
* along with this program. If not, see http://www.gnu.org/licenses/ .
|
|
|
|
*
|
|
|
|
* Author: Carl Worth <cworth@cworth.org>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "notmuch-client.h"
|
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
|
|
|
format_message_text (unused (const void *ctx),
|
|
|
|
notmuch_message_t *message,
|
|
|
|
int indent);
|
|
|
|
static void
|
|
|
|
format_headers_text (const void *ctx,
|
|
|
|
notmuch_message_t *message);
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
|
2011-06-06 02:29:28 +02:00
|
|
|
static void
|
|
|
|
format_headers_message_part_text (GMimeMessage *message);
|
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_start_text (GMimeObject *part,
|
|
|
|
int *part_count);
|
|
|
|
|
|
|
|
static void
|
|
|
|
format_part_content_text (GMimeObject *part);
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
|
|
|
|
static void
|
|
|
|
format_part_end_text (GMimeObject *part);
|
|
|
|
|
2011-05-20 20:45:33 +02:00
|
|
|
static const notmuch_show_format_t format_text = {
|
2012-01-24 00:33:10 +01:00
|
|
|
"", NULL,
|
2009-12-31 16:17:40 +01:00
|
|
|
"\fmessage{ ", format_message_text,
|
2011-06-06 02:29:28 +02:00
|
|
|
"\fheader{\n", format_headers_text, format_headers_message_part_text, "\fheader}\n",
|
2011-05-26 03:01:13 +02:00
|
|
|
"\fbody{\n",
|
|
|
|
format_part_start_text,
|
2011-05-26 03:01:17 +02:00
|
|
|
NULL,
|
2011-05-26 03:01:18 +02:00
|
|
|
NULL,
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_content_text,
|
|
|
|
format_part_end_text,
|
|
|
|
"",
|
|
|
|
"\fbody}\n",
|
2009-12-31 16:17:40 +01:00
|
|
|
"\fmessage}\n", "",
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
format_message_json (const void *ctx,
|
|
|
|
notmuch_message_t *message,
|
|
|
|
unused (int indent));
|
|
|
|
static void
|
|
|
|
format_headers_json (const void *ctx,
|
|
|
|
notmuch_message_t *message);
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
|
2011-06-06 02:29:28 +02:00
|
|
|
static void
|
|
|
|
format_headers_message_part_json (GMimeMessage *message);
|
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_start_json (unused (GMimeObject *part),
|
|
|
|
int *part_count);
|
|
|
|
|
2011-05-26 03:01:18 +02:00
|
|
|
static void
|
|
|
|
format_part_encstatus_json (int status);
|
|
|
|
|
2011-05-26 03:01:17 +02:00
|
|
|
static void
|
2012-01-20 10:39:24 +01:00
|
|
|
#ifdef GMIME_ATLEAST_26
|
|
|
|
format_part_sigstatus_json (GMimeSignatureList* siglist);
|
|
|
|
#else
|
2011-05-26 03:01:17 +02:00
|
|
|
format_part_sigstatus_json (const GMimeSignatureValidity* validity);
|
2012-01-20 10:39:24 +01:00
|
|
|
#endif
|
2011-05-26 03:01:17 +02:00
|
|
|
|
2011-05-26 03:01:13 +02:00
|
|
|
static void
|
|
|
|
format_part_content_json (GMimeObject *part);
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
|
|
|
|
static void
|
|
|
|
format_part_end_json (GMimeObject *part);
|
|
|
|
|
2011-05-20 20:45:33 +02:00
|
|
|
static const notmuch_show_format_t format_json = {
|
2012-01-24 00:33:10 +01:00
|
|
|
"[", NULL,
|
2009-12-31 16:17:40 +01:00
|
|
|
"{", format_message_json,
|
2011-06-06 02:29:28 +02:00
|
|
|
"\"headers\": {", format_headers_json, format_headers_message_part_json, "}",
|
2011-05-26 03:01:13 +02:00
|
|
|
", \"body\": [",
|
|
|
|
format_part_start_json,
|
2011-05-26 03:01:18 +02:00
|
|
|
format_part_encstatus_json,
|
2011-05-26 03:01:17 +02:00
|
|
|
format_part_sigstatus_json,
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_content_json,
|
|
|
|
format_part_end_json,
|
|
|
|
", ",
|
|
|
|
"]",
|
2009-12-31 16:17:40 +01:00
|
|
|
"}", ", ",
|
|
|
|
"]"
|
|
|
|
};
|
|
|
|
|
2010-06-09 04:45:54 +02:00
|
|
|
static void
|
|
|
|
format_message_mbox (const void *ctx,
|
|
|
|
notmuch_message_t *message,
|
|
|
|
unused (int indent));
|
|
|
|
|
2011-05-20 20:45:33 +02:00
|
|
|
static const notmuch_show_format_t format_mbox = {
|
2012-01-24 00:33:10 +01:00
|
|
|
"", NULL,
|
2010-06-09 04:45:54 +02:00
|
|
|
"", format_message_mbox,
|
2011-06-06 02:29:28 +02:00
|
|
|
"", NULL, NULL, "",
|
2011-05-26 03:01:13 +02:00
|
|
|
"",
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
2011-05-26 03:01:17 +02:00
|
|
|
NULL,
|
2011-05-26 03:01:18 +02:00
|
|
|
NULL,
|
2011-05-26 03:01:13 +02:00
|
|
|
"",
|
|
|
|
"",
|
2010-06-09 04:45:54 +02:00
|
|
|
"", "",
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
static void
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_content_raw (GMimeObject *part);
|
2011-05-24 00:31:32 +02:00
|
|
|
|
|
|
|
static const notmuch_show_format_t format_raw = {
|
2012-01-24 00:33:10 +01:00
|
|
|
"", NULL,
|
2011-05-24 00:31:32 +02:00
|
|
|
"", NULL,
|
2011-06-06 02:29:28 +02:00
|
|
|
"", NULL, format_headers_message_part_text, "\n",
|
2011-05-26 03:01:13 +02:00
|
|
|
"",
|
2011-05-26 03:01:18 +02:00
|
|
|
NULL,
|
2011-05-26 03:01:17 +02:00
|
|
|
NULL,
|
2011-05-26 03:01:13 +02:00
|
|
|
NULL,
|
|
|
|
format_part_content_raw,
|
|
|
|
NULL,
|
|
|
|
"",
|
|
|
|
"",
|
2011-05-24 00:31:32 +02:00
|
|
|
"", "",
|
|
|
|
""
|
|
|
|
};
|
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2009-11-21 01:09:36 +01:00
|
|
|
static void
|
2009-12-31 16:17:40 +01:00
|
|
|
format_message_text (unused (const void *ctx), notmuch_message_t *message, int indent)
|
|
|
|
{
|
|
|
|
printf ("id:%s depth:%d match:%d filename:%s\n",
|
|
|
|
notmuch_message_get_message_id (message),
|
|
|
|
indent,
|
|
|
|
notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH),
|
|
|
|
notmuch_message_get_filename (message));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
format_message_json (const void *ctx, notmuch_message_t *message, unused (int indent))
|
|
|
|
{
|
2010-03-11 13:11:43 +01:00
|
|
|
notmuch_tags_t *tags;
|
|
|
|
int first = 1;
|
2009-12-31 16:17:40 +01:00
|
|
|
void *ctx_quote = talloc_new (ctx);
|
2010-03-23 10:40:48 +01:00
|
|
|
time_t date;
|
|
|
|
const char *relative_date;
|
|
|
|
|
|
|
|
date = notmuch_message_get_date (message);
|
|
|
|
relative_date = notmuch_time_relative_date (ctx, date);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2010-04-16 13:47:49 +02:00
|
|
|
printf ("\"id\": %s, \"match\": %s, \"filename\": %s, \"timestamp\": %ld, \"date_relative\": \"%s\", \"tags\": [",
|
2009-12-31 16:17:40 +01:00
|
|
|
json_quote_str (ctx_quote, notmuch_message_get_message_id (message)),
|
|
|
|
notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? "true" : "false",
|
2010-03-23 10:40:48 +01:00
|
|
|
json_quote_str (ctx_quote, notmuch_message_get_filename (message)),
|
|
|
|
date, relative_date);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
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))
|
|
|
|
{
|
|
|
|
printf("%s%s", first ? "" : ",",
|
|
|
|
json_quote_str (ctx_quote, notmuch_tags_get (tags)));
|
|
|
|
first = 0;
|
|
|
|
}
|
2011-06-06 02:29:28 +02:00
|
|
|
printf("], ");
|
2009-12-31 16:17:40 +01:00
|
|
|
talloc_free (ctx_quote);
|
|
|
|
}
|
|
|
|
|
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";
|
|
|
|
|
|
|
|
addresses = internet_address_list_parse_string (from);
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Print a message in "mboxrd" format as documented, for example,
|
|
|
|
* here:
|
|
|
|
*
|
|
|
|
* http://qmail.org/qmail-manual-html/man5/mbox.html
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
format_message_mbox (const void *ctx,
|
|
|
|
notmuch_message_t *message,
|
|
|
|
unused (int indent))
|
|
|
|
{
|
|
|
|
const char *filename;
|
|
|
|
FILE *file;
|
|
|
|
const char *from;
|
|
|
|
|
|
|
|
time_t date;
|
|
|
|
struct tm date_gmtime;
|
|
|
|
char date_asctime[26];
|
|
|
|
|
|
|
|
char *line = NULL;
|
|
|
|
size_t line_size;
|
|
|
|
ssize_t line_len;
|
|
|
|
|
|
|
|
filename = notmuch_message_get_filename (message);
|
|
|
|
file = fopen (filename, "r");
|
|
|
|
if (file == NULL) {
|
|
|
|
fprintf (stderr, "Failed to open %s: %s\n",
|
|
|
|
filename, strerror (errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
while ((line_len = getline (&line, &line_size, file)) != -1 ) {
|
|
|
|
if (_is_from_line (line))
|
|
|
|
putchar ('>');
|
|
|
|
printf ("%s", line);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf ("\n");
|
|
|
|
|
|
|
|
fclose (file);
|
|
|
|
}
|
|
|
|
|
2011-06-06 02:29:28 +02:00
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
|
|
|
format_headers_text (const void *ctx, notmuch_message_t *message)
|
|
|
|
{
|
|
|
|
const char *headers[] = {
|
|
|
|
"Subject", "From", "To", "Cc", "Bcc", "Date"
|
|
|
|
};
|
|
|
|
const char *name, *value;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
printf ("%s\n", _get_one_line_summary (ctx, message));
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE (headers); i++) {
|
|
|
|
name = headers[i];
|
|
|
|
value = notmuch_message_get_header (message, name);
|
2010-03-09 19:12:58 +01:00
|
|
|
if (value && strlen (value))
|
2009-12-31 16:17:40 +01:00
|
|
|
printf ("%s: %s\n", name, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-06 02:29:28 +02:00
|
|
|
static void
|
|
|
|
format_headers_message_part_text (GMimeMessage *message)
|
|
|
|
{
|
|
|
|
InternetAddressList *recipients;
|
|
|
|
const char *recipients_string;
|
|
|
|
|
show: Use consistent header ordering in the text format
Previously, top-level message headers were printed as Subject, From,
To, Date, while embedded message headers were printed From, To,
Subject, Date. This makes both cases use the former order and updates
the tests accordingly.
Strangely, the raw format also uses this function, so this also fixes
the two raw format tests affected by this change.
2012-01-23 03:31:12 +01:00
|
|
|
printf ("Subject: %s\n", g_mime_message_get_subject (message));
|
2011-06-06 02:29:28 +02:00
|
|
|
printf ("From: %s\n", g_mime_message_get_sender (message));
|
|
|
|
recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO);
|
|
|
|
recipients_string = internet_address_list_to_string (recipients, 0);
|
|
|
|
if (recipients_string)
|
|
|
|
printf ("To: %s\n",
|
|
|
|
recipients_string);
|
|
|
|
recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC);
|
|
|
|
recipients_string = internet_address_list_to_string (recipients, 0);
|
|
|
|
if (recipients_string)
|
|
|
|
printf ("Cc: %s\n",
|
|
|
|
recipients_string);
|
|
|
|
printf ("Date: %s\n", g_mime_message_get_date_as_string (message));
|
|
|
|
}
|
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
|
|
|
format_headers_json (const void *ctx, notmuch_message_t *message)
|
|
|
|
{
|
|
|
|
const char *headers[] = {
|
|
|
|
"Subject", "From", "To", "Cc", "Bcc", "Date"
|
|
|
|
};
|
|
|
|
const char *name, *value;
|
|
|
|
unsigned int i;
|
|
|
|
int first_header = 1;
|
|
|
|
void *ctx_quote = talloc_new (ctx);
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE (headers); i++) {
|
|
|
|
name = headers[i];
|
|
|
|
value = notmuch_message_get_header (message, name);
|
|
|
|
if (value)
|
|
|
|
{
|
|
|
|
if (!first_header)
|
|
|
|
fputs (", ", stdout);
|
|
|
|
first_header = 0;
|
|
|
|
|
|
|
|
printf ("%s: %s",
|
|
|
|
json_quote_str (ctx_quote, name),
|
|
|
|
json_quote_str (ctx_quote, value));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
talloc_free (ctx_quote);
|
|
|
|
}
|
|
|
|
|
2011-06-06 02:29:28 +02:00
|
|
|
static void
|
|
|
|
format_headers_message_part_json (GMimeMessage *message)
|
|
|
|
{
|
|
|
|
void *ctx = talloc_new (NULL);
|
|
|
|
void *ctx_quote = talloc_new (ctx);
|
|
|
|
InternetAddressList *recipients;
|
|
|
|
const char *recipients_string;
|
|
|
|
|
|
|
|
printf ("%s: %s",
|
|
|
|
json_quote_str (ctx_quote, "From"),
|
|
|
|
json_quote_str (ctx_quote, g_mime_message_get_sender (message)));
|
|
|
|
recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_TO);
|
|
|
|
recipients_string = internet_address_list_to_string (recipients, 0);
|
|
|
|
if (recipients_string)
|
|
|
|
printf (", %s: %s",
|
|
|
|
json_quote_str (ctx_quote, "To"),
|
|
|
|
json_quote_str (ctx_quote, recipients_string));
|
|
|
|
recipients = g_mime_message_get_recipients (message, GMIME_RECIPIENT_TYPE_CC);
|
|
|
|
recipients_string = internet_address_list_to_string (recipients, 0);
|
|
|
|
if (recipients_string)
|
|
|
|
printf (", %s: %s",
|
|
|
|
json_quote_str (ctx_quote, "Cc"),
|
|
|
|
json_quote_str (ctx_quote, recipients_string));
|
|
|
|
printf (", %s: %s",
|
|
|
|
json_quote_str (ctx_quote, "Subject"),
|
|
|
|
json_quote_str (ctx_quote, g_mime_message_get_subject (message)));
|
|
|
|
printf (", %s: %s",
|
|
|
|
json_quote_str (ctx_quote, "Date"),
|
|
|
|
json_quote_str (ctx_quote, g_mime_message_get_date_as_string (message)));
|
|
|
|
|
|
|
|
talloc_free (ctx_quote);
|
|
|
|
talloc_free (ctx);
|
|
|
|
}
|
|
|
|
|
2011-06-01 01:13:21 +02:00
|
|
|
/* Write a MIME text part out to the given stream.
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
2011-06-01 01:13:21 +02:00
|
|
|
show_text_part_content (GMimeObject *part, GMimeStream *stream_out)
|
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;
|
|
|
|
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.",
|
|
|
|
g_mime_content_type_to_string (content_type));
|
|
|
|
|
2011-06-01 01:18:45 +02:00
|
|
|
if (stream_out == NULL)
|
|
|
|
return;
|
2009-11-21 01:09:36 +01:00
|
|
|
|
2011-06-01 01:18:45 +02:00
|
|
|
stream_filter = g_mime_stream_filter_new (stream_out);
|
|
|
|
g_mime_stream_filter_add(GMIME_STREAM_FILTER (stream_filter),
|
|
|
|
g_mime_filter_crlf_new (FALSE, FALSE));
|
|
|
|
|
|
|
|
charset = g_mime_object_get_content_type_parameter (part, "charset");
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
wrapper = g_mime_part_get_content_object (GMIME_PART (part));
|
|
|
|
if (wrapper && stream_filter)
|
|
|
|
g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
|
|
|
|
if (stream_filter)
|
|
|
|
g_object_unref(stream_filter);
|
|
|
|
}
|
|
|
|
|
2012-01-20 10:39:24 +01:00
|
|
|
#ifdef GMIME_ATLEAST_26
|
|
|
|
static const char*
|
|
|
|
signature_status_to_string (GMimeSignatureStatus x)
|
|
|
|
{
|
|
|
|
switch (x) {
|
|
|
|
case GMIME_SIGNATURE_STATUS_GOOD:
|
|
|
|
return "good";
|
|
|
|
case GMIME_SIGNATURE_STATUS_BAD:
|
|
|
|
return "bad";
|
|
|
|
case GMIME_SIGNATURE_STATUS_ERROR:
|
|
|
|
return "error";
|
|
|
|
}
|
|
|
|
return "unknown";
|
|
|
|
}
|
|
|
|
#else
|
2011-05-26 03:01:17 +02:00
|
|
|
static const char*
|
2011-06-01 01:20:56 +02:00
|
|
|
signer_status_to_string (GMimeSignerStatus x)
|
2011-05-26 03:01:17 +02:00
|
|
|
{
|
|
|
|
switch (x) {
|
|
|
|
case GMIME_SIGNER_STATUS_NONE:
|
|
|
|
return "none";
|
|
|
|
case GMIME_SIGNER_STATUS_GOOD:
|
|
|
|
return "good";
|
|
|
|
case GMIME_SIGNER_STATUS_BAD:
|
|
|
|
return "bad";
|
|
|
|
case GMIME_SIGNER_STATUS_ERROR:
|
|
|
|
return "error";
|
|
|
|
}
|
|
|
|
return "unknown";
|
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
#endif
|
2011-05-26 03:01:17 +02:00
|
|
|
|
2009-11-10 21:03:05 +01:00
|
|
|
static void
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_start_text (GMimeObject *part, int *part_count)
|
2009-11-10 21:03:05 +01:00
|
|
|
{
|
2011-05-26 03:01:13 +02:00
|
|
|
GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (part);
|
2009-11-10 21:03:05 +01:00
|
|
|
|
|
|
|
if (disposition &&
|
|
|
|
strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
|
|
|
|
{
|
2011-05-26 03:01:13 +02:00
|
|
|
printf ("\fattachment{ ID: %d", *part_count);
|
2009-11-21 01:09:36 +01:00
|
|
|
|
2011-05-26 03:01:13 +02:00
|
|
|
} else {
|
2009-11-21 01:09:36 +01:00
|
|
|
|
2011-05-26 03:01:13 +02:00
|
|
|
printf ("\fpart{ ID: %d", *part_count);
|
2009-11-10 21:03:05 +01:00
|
|
|
}
|
2011-05-26 03:01:13 +02:00
|
|
|
}
|
2009-11-10 21:03:05 +01:00
|
|
|
|
2011-05-26 03:01:13 +02:00
|
|
|
static void
|
|
|
|
format_part_content_text (GMimeObject *part)
|
|
|
|
{
|
Add part filename and content-id in notmuch show output if available.
Before the change, notmuch show output had filename only for
parts with "Content-Disposition: attachment". But parts with
inline disposition may have filename as well.
The patch makes notmuch show always output filename if available,
independent of Content-Disposition. Both JSON and text output
formats are changed.
Also, the patch adds Content-id to text output format of notmuch
show.
The main goal of these changes is to have filenames on Emacs
buttons for inline attachments. In particular, this is very
helpful for inline patches.
Note: text format changes may require updates in clients that use
it. The changes are:
* text part header format changed from:
^Lpart{ ID: 2, Content-type: text/x-diff
to:
^Lpart{ ID: 2, Filename: cool-feature.patch, Content-type: text/x-diff
* attachment format changed from:
^Lattachment{ ID: 4, Content-type: application/octet-stream
Attachment: data.tar.bz2 (application/octet-stream)
Non-text part: application/octet-stream
^Lattachment}
to:
^Lattachment{ ID: 4, Filename: data.tar.bz2, Content-type: application/octet-stream
Non-text part: application/octet-stream
^Lattachment}
2011-05-29 00:03:47 +02:00
|
|
|
const char *cid = g_mime_object_get_content_id (part);
|
2011-05-26 03:01:13 +02:00
|
|
|
GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
|
2009-11-10 21:03:05 +01:00
|
|
|
|
Add part filename and content-id in notmuch show output if available.
Before the change, notmuch show output had filename only for
parts with "Content-Disposition: attachment". But parts with
inline disposition may have filename as well.
The patch makes notmuch show always output filename if available,
independent of Content-Disposition. Both JSON and text output
formats are changed.
Also, the patch adds Content-id to text output format of notmuch
show.
The main goal of these changes is to have filenames on Emacs
buttons for inline attachments. In particular, this is very
helpful for inline patches.
Note: text format changes may require updates in clients that use
it. The changes are:
* text part header format changed from:
^Lpart{ ID: 2, Content-type: text/x-diff
to:
^Lpart{ ID: 2, Filename: cool-feature.patch, Content-type: text/x-diff
* attachment format changed from:
^Lattachment{ ID: 4, Content-type: application/octet-stream
Attachment: data.tar.bz2 (application/octet-stream)
Non-text part: application/octet-stream
^Lattachment}
to:
^Lattachment{ ID: 4, Filename: data.tar.bz2, Content-type: application/octet-stream
Non-text part: application/octet-stream
^Lattachment}
2011-05-29 00:03:47 +02:00
|
|
|
if (GMIME_IS_PART (part))
|
2011-05-26 03:01:13 +02:00
|
|
|
{
|
|
|
|
const char *filename = g_mime_part_get_filename (GMIME_PART (part));
|
Add part filename and content-id in notmuch show output if available.
Before the change, notmuch show output had filename only for
parts with "Content-Disposition: attachment". But parts with
inline disposition may have filename as well.
The patch makes notmuch show always output filename if available,
independent of Content-Disposition. Both JSON and text output
formats are changed.
Also, the patch adds Content-id to text output format of notmuch
show.
The main goal of these changes is to have filenames on Emacs
buttons for inline attachments. In particular, this is very
helpful for inline patches.
Note: text format changes may require updates in clients that use
it. The changes are:
* text part header format changed from:
^Lpart{ ID: 2, Content-type: text/x-diff
to:
^Lpart{ ID: 2, Filename: cool-feature.patch, Content-type: text/x-diff
* attachment format changed from:
^Lattachment{ ID: 4, Content-type: application/octet-stream
Attachment: data.tar.bz2 (application/octet-stream)
Non-text part: application/octet-stream
^Lattachment}
to:
^Lattachment{ ID: 4, Filename: data.tar.bz2, Content-type: application/octet-stream
Non-text part: application/octet-stream
^Lattachment}
2011-05-29 00:03:47 +02:00
|
|
|
if (filename)
|
|
|
|
printf (", Filename: %s", filename);
|
2011-05-26 03:01:13 +02:00
|
|
|
}
|
2009-11-10 21:03:05 +01:00
|
|
|
|
Add part filename and content-id in notmuch show output if available.
Before the change, notmuch show output had filename only for
parts with "Content-Disposition: attachment". But parts with
inline disposition may have filename as well.
The patch makes notmuch show always output filename if available,
independent of Content-Disposition. Both JSON and text output
formats are changed.
Also, the patch adds Content-id to text output format of notmuch
show.
The main goal of these changes is to have filenames on Emacs
buttons for inline attachments. In particular, this is very
helpful for inline patches.
Note: text format changes may require updates in clients that use
it. The changes are:
* text part header format changed from:
^Lpart{ ID: 2, Content-type: text/x-diff
to:
^Lpart{ ID: 2, Filename: cool-feature.patch, Content-type: text/x-diff
* attachment format changed from:
^Lattachment{ ID: 4, Content-type: application/octet-stream
Attachment: data.tar.bz2 (application/octet-stream)
Non-text part: application/octet-stream
^Lattachment}
to:
^Lattachment{ ID: 4, Filename: data.tar.bz2, Content-type: application/octet-stream
Non-text part: application/octet-stream
^Lattachment}
2011-05-29 00:03:47 +02:00
|
|
|
if (cid)
|
|
|
|
printf (", Content-id: %s", cid);
|
|
|
|
|
|
|
|
printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type));
|
|
|
|
|
2009-11-10 21:03:05 +01:00
|
|
|
if (g_mime_content_type_is_type (content_type, "text", "*") &&
|
|
|
|
!g_mime_content_type_is_type (content_type, "text", "html"))
|
|
|
|
{
|
2011-05-28 23:52:00 +02:00
|
|
|
GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
|
2010-04-01 15:47:21 +02:00
|
|
|
g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
|
2011-06-01 01:13:21 +02:00
|
|
|
show_text_part_content (part, stream_stdout);
|
2010-04-01 15:47:21 +02:00
|
|
|
g_object_unref(stream_stdout);
|
2009-11-10 21:03:05 +01:00
|
|
|
}
|
2011-05-23 09:46:16 +02:00
|
|
|
else if (g_mime_content_type_is_type (content_type, "multipart", "*") ||
|
|
|
|
g_mime_content_type_is_type (content_type, "message", "rfc822"))
|
2011-05-17 07:28:36 +02:00
|
|
|
{
|
|
|
|
/* Do nothing for multipart since its content will be printed
|
|
|
|
* when recursing. */
|
|
|
|
}
|
2009-11-10 21:03:05 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
printf ("Non-text part: %s\n",
|
|
|
|
g_mime_content_type_to_string (content_type));
|
|
|
|
}
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
}
|
2009-11-10 21:03:05 +01:00
|
|
|
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
static void
|
|
|
|
format_part_end_text (GMimeObject *part)
|
|
|
|
{
|
|
|
|
GMimeContentDisposition *disposition;
|
|
|
|
|
|
|
|
disposition = g_mime_object_get_content_disposition (part);
|
|
|
|
if (disposition &&
|
|
|
|
strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
|
|
|
|
{
|
|
|
|
printf ("\fattachment}\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printf ("\fpart}\n");
|
|
|
|
}
|
2009-11-10 21:03:05 +01:00
|
|
|
}
|
|
|
|
|
2009-11-16 05:39:25 +01:00
|
|
|
static void
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_start_json (unused (GMimeObject *part), int *part_count)
|
2009-11-16 05:39:25 +01:00
|
|
|
{
|
2011-05-26 03:01:13 +02:00
|
|
|
printf ("{\"id\": %d", *part_count);
|
|
|
|
}
|
|
|
|
|
2011-05-26 03:01:18 +02:00
|
|
|
static void
|
|
|
|
format_part_encstatus_json (int status)
|
|
|
|
{
|
|
|
|
printf (", \"encstatus\": [{\"status\": ");
|
|
|
|
if (status) {
|
|
|
|
printf ("\"good\"");
|
|
|
|
} else {
|
|
|
|
printf ("\"bad\"");
|
|
|
|
}
|
|
|
|
printf ("}]");
|
|
|
|
}
|
|
|
|
|
2012-01-20 10:39:24 +01:00
|
|
|
#ifdef GMIME_ATLEAST_26
|
|
|
|
static void
|
|
|
|
format_part_sigstatus_json (GMimeSignatureList *siglist)
|
|
|
|
{
|
|
|
|
printf (", \"sigstatus\": [");
|
|
|
|
|
|
|
|
if (!siglist) {
|
|
|
|
printf ("]");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *ctx_quote = talloc_new (NULL);
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < g_mime_signature_list_length (siglist); i++) {
|
|
|
|
GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i);
|
|
|
|
|
|
|
|
if (i > 0)
|
|
|
|
printf (", ");
|
|
|
|
|
|
|
|
printf ("{");
|
|
|
|
|
|
|
|
/* status */
|
|
|
|
GMimeSignatureStatus status = g_mime_signature_get_status (signature);
|
|
|
|
printf ("\"status\": %s",
|
|
|
|
json_quote_str (ctx_quote,
|
|
|
|
signature_status_to_string (status)));
|
|
|
|
|
|
|
|
GMimeCertificate *certificate = g_mime_signature_get_certificate (signature);
|
|
|
|
if (status == GMIME_SIGNATURE_STATUS_GOOD) {
|
|
|
|
if (certificate)
|
|
|
|
printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, g_mime_certificate_get_fingerprint (certificate)));
|
|
|
|
/* 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);
|
|
|
|
if (created != -1)
|
|
|
|
printf (", \"created\": %d", (int) created);
|
|
|
|
time_t expires = g_mime_signature_get_expires (signature);
|
|
|
|
if (expires > 0)
|
|
|
|
printf (", \"expires\": %d", (int) expires);
|
|
|
|
/* output user id only if validity is FULL or ULTIMATE. */
|
|
|
|
/* note that gmime is using the term "trust" here, which
|
|
|
|
* is WRONG. It's actually user id "validity". */
|
|
|
|
if (certificate) {
|
|
|
|
const char *name = g_mime_certificate_get_name (certificate);
|
|
|
|
GMimeCertificateTrust trust = g_mime_certificate_get_trust (certificate);
|
|
|
|
if (name && (trust == GMIME_CERTIFICATE_TRUST_FULLY || trust == GMIME_CERTIFICATE_TRUST_ULTIMATE))
|
|
|
|
printf (", \"userid\": %s", json_quote_str (ctx_quote, name));
|
|
|
|
}
|
|
|
|
} else if (certificate) {
|
|
|
|
const char *key_id = g_mime_certificate_get_key_id (certificate);
|
|
|
|
if (key_id)
|
|
|
|
printf (", \"keyid\": %s", json_quote_str (ctx_quote, key_id));
|
|
|
|
}
|
|
|
|
|
|
|
|
GMimeSignatureError errors = g_mime_signature_get_errors (signature);
|
|
|
|
if (errors != GMIME_SIGNATURE_ERROR_NONE) {
|
|
|
|
printf (", \"errors\": %d", errors);
|
|
|
|
}
|
|
|
|
|
|
|
|
printf ("}");
|
|
|
|
}
|
|
|
|
|
|
|
|
printf ("]");
|
|
|
|
|
|
|
|
talloc_free (ctx_quote);
|
|
|
|
}
|
|
|
|
#else
|
2011-05-26 03:01:17 +02:00
|
|
|
static void
|
|
|
|
format_part_sigstatus_json (const GMimeSignatureValidity* validity)
|
|
|
|
{
|
|
|
|
printf (", \"sigstatus\": [");
|
|
|
|
|
|
|
|
if (!validity) {
|
|
|
|
printf ("]");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);
|
|
|
|
int first = 1;
|
|
|
|
void *ctx_quote = talloc_new (NULL);
|
|
|
|
|
|
|
|
while (signer) {
|
|
|
|
if (first)
|
|
|
|
first = 0;
|
|
|
|
else
|
|
|
|
printf (", ");
|
|
|
|
|
|
|
|
printf ("{");
|
|
|
|
|
|
|
|
/* status */
|
2011-06-01 01:20:56 +02:00
|
|
|
printf ("\"status\": %s",
|
|
|
|
json_quote_str (ctx_quote,
|
|
|
|
signer_status_to_string (signer->status)));
|
2011-05-26 03:01:17 +02:00
|
|
|
|
|
|
|
if (signer->status == GMIME_SIGNER_STATUS_GOOD)
|
|
|
|
{
|
|
|
|
if (signer->fingerprint)
|
|
|
|
printf (", \"fingerprint\": %s", json_quote_str (ctx_quote, signer->fingerprint));
|
|
|
|
/* these dates are seconds since the epoch; should we
|
|
|
|
* provide a more human-readable format string? */
|
|
|
|
if (signer->created)
|
|
|
|
printf (", \"created\": %d", (int) signer->created);
|
|
|
|
if (signer->expires)
|
|
|
|
printf (", \"expires\": %d", (int) signer->expires);
|
|
|
|
/* output user id only if validity is FULL or ULTIMATE. */
|
|
|
|
/* note that gmime is using the term "trust" here, which
|
|
|
|
* is WRONG. It's actually user id "validity". */
|
|
|
|
if ((signer->name) && (signer->trust)) {
|
|
|
|
if ((signer->trust == GMIME_SIGNER_TRUST_FULLY) || (signer->trust == GMIME_SIGNER_TRUST_ULTIMATE))
|
|
|
|
printf (", \"userid\": %s", json_quote_str (ctx_quote, signer->name));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (signer->keyid)
|
|
|
|
printf (", \"keyid\": %s", json_quote_str (ctx_quote, signer->keyid));
|
|
|
|
}
|
|
|
|
if (signer->errors != GMIME_SIGNER_ERROR_NONE) {
|
2012-01-22 01:20:57 +01:00
|
|
|
printf (", \"errors\": %d", signer->errors);
|
2011-05-26 03:01:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
printf ("}");
|
|
|
|
signer = signer->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf ("]");
|
|
|
|
|
|
|
|
talloc_free (ctx_quote);
|
|
|
|
}
|
2012-01-20 10:39:24 +01:00
|
|
|
#endif
|
2011-05-26 03:01:17 +02:00
|
|
|
|
2011-05-26 03:01:13 +02:00
|
|
|
static void
|
|
|
|
format_part_content_json (GMimeObject *part)
|
|
|
|
{
|
|
|
|
GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
|
2009-12-31 16:17:40 +01:00
|
|
|
GMimeStream *stream_memory = g_mime_stream_mem_new ();
|
2011-05-26 03:01:13 +02:00
|
|
|
const char *cid = g_mime_object_get_content_id (part);
|
|
|
|
void *ctx = talloc_new (NULL);
|
2009-12-31 16:17:40 +01:00
|
|
|
GByteArray *part_content;
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2011-05-26 03:01:13 +02:00
|
|
|
printf (", \"content-type\": %s",
|
2009-12-31 16:17:40 +01:00
|
|
|
json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2010-05-17 14:38:06 +02:00
|
|
|
if (cid != NULL)
|
2011-05-26 03:01:13 +02:00
|
|
|
printf(", \"content-id\": %s", json_quote_str (ctx, cid));
|
2010-05-17 14:38:06 +02:00
|
|
|
|
Add part filename and content-id in notmuch show output if available.
Before the change, notmuch show output had filename only for
parts with "Content-Disposition: attachment". But parts with
inline disposition may have filename as well.
The patch makes notmuch show always output filename if available,
independent of Content-Disposition. Both JSON and text output
formats are changed.
Also, the patch adds Content-id to text output format of notmuch
show.
The main goal of these changes is to have filenames on Emacs
buttons for inline attachments. In particular, this is very
helpful for inline patches.
Note: text format changes may require updates in clients that use
it. The changes are:
* text part header format changed from:
^Lpart{ ID: 2, Content-type: text/x-diff
to:
^Lpart{ ID: 2, Filename: cool-feature.patch, Content-type: text/x-diff
* attachment format changed from:
^Lattachment{ ID: 4, Content-type: application/octet-stream
Attachment: data.tar.bz2 (application/octet-stream)
Non-text part: application/octet-stream
^Lattachment}
to:
^Lattachment{ ID: 4, Filename: data.tar.bz2, Content-type: application/octet-stream
Non-text part: application/octet-stream
^Lattachment}
2011-05-29 00:03:47 +02:00
|
|
|
if (GMIME_IS_PART (part))
|
2009-12-31 16:17:40 +01:00
|
|
|
{
|
|
|
|
const char *filename = g_mime_part_get_filename (GMIME_PART (part));
|
Add part filename and content-id in notmuch show output if available.
Before the change, notmuch show output had filename only for
parts with "Content-Disposition: attachment". But parts with
inline disposition may have filename as well.
The patch makes notmuch show always output filename if available,
independent of Content-Disposition. Both JSON and text output
formats are changed.
Also, the patch adds Content-id to text output format of notmuch
show.
The main goal of these changes is to have filenames on Emacs
buttons for inline attachments. In particular, this is very
helpful for inline patches.
Note: text format changes may require updates in clients that use
it. The changes are:
* text part header format changed from:
^Lpart{ ID: 2, Content-type: text/x-diff
to:
^Lpart{ ID: 2, Filename: cool-feature.patch, Content-type: text/x-diff
* attachment format changed from:
^Lattachment{ ID: 4, Content-type: application/octet-stream
Attachment: data.tar.bz2 (application/octet-stream)
Non-text part: application/octet-stream
^Lattachment}
to:
^Lattachment{ ID: 4, Filename: data.tar.bz2, Content-type: application/octet-stream
Non-text part: application/octet-stream
^Lattachment}
2011-05-29 00:03:47 +02:00
|
|
|
if (filename)
|
|
|
|
printf (", \"filename\": %s", json_quote_str (ctx, filename));
|
2009-11-16 05:39:25 +01:00
|
|
|
}
|
|
|
|
|
2012-01-13 10:44:46 +01:00
|
|
|
if (g_mime_content_type_is_type (content_type, "text", "*"))
|
2009-12-31 16:17:40 +01:00
|
|
|
{
|
2012-01-14 04:08:23 +01:00
|
|
|
/* For non-HTML text parts, we include the content in the
|
2012-01-13 10:44:46 +01:00
|
|
|
* JSON. Since JSON must be Unicode, we handle charset
|
|
|
|
* decoding here and do not report a charset to the caller.
|
|
|
|
* For text/html parts, we do not include the content. If a
|
|
|
|
* caller is interested in text/html parts, it should retrieve
|
|
|
|
* them separately and they will not be decoded. Since this
|
|
|
|
* makes charset decoding the responsibility on the caller, we
|
|
|
|
* report the charset for text/html parts.
|
|
|
|
*/
|
|
|
|
if (g_mime_content_type_is_type (content_type, "text", "html"))
|
|
|
|
{
|
|
|
|
const char *content_charset = g_mime_object_get_content_type_parameter (GMIME_OBJECT (part), "charset");
|
|
|
|
|
|
|
|
if (content_charset != NULL)
|
|
|
|
printf (", \"content-charset\": %s", json_quote_str (ctx, content_charset));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
show_text_part_content (part, stream_memory);
|
|
|
|
part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
|
2009-12-31 16:17:40 +01:00
|
|
|
|
2012-01-13 10:44:46 +01:00
|
|
|
printf (", \"content\": %s", json_quote_chararray (ctx, (char *) part_content->data, part_content->len));
|
|
|
|
}
|
2009-12-31 16:17:40 +01:00
|
|
|
}
|
2011-06-06 02:29:28 +02:00
|
|
|
else if (g_mime_content_type_is_type (content_type, "multipart", "*"))
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
{
|
|
|
|
printf (", \"content\": [");
|
|
|
|
}
|
2011-06-06 02:29:28 +02:00
|
|
|
else if (g_mime_content_type_is_type (content_type, "message", "rfc822"))
|
|
|
|
{
|
|
|
|
printf (", \"content\": [{");
|
|
|
|
}
|
2009-12-31 16:17:40 +01:00
|
|
|
|
|
|
|
talloc_free (ctx);
|
|
|
|
if (stream_memory)
|
|
|
|
g_object_unref (stream_memory);
|
|
|
|
}
|
|
|
|
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
static void
|
|
|
|
format_part_end_json (GMimeObject *part)
|
|
|
|
{
|
2011-06-06 02:29:28 +02:00
|
|
|
GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
|
2011-06-06 02:29:28 +02:00
|
|
|
if (g_mime_content_type_is_type (content_type, "multipart", "*"))
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
printf ("]");
|
2011-06-06 02:29:28 +02:00
|
|
|
else if (g_mime_content_type_is_type (content_type, "message", "rfc822"))
|
|
|
|
printf ("}]");
|
notmuch show: Properly nest MIME parts within mulipart parts
Previously, notmuch show flattened all output, losing information
about the nesting of the MIME hierarchy. Now, the output is properly
nested, (both in the --format=text and --format=json output), so that
clients can analyze the original MIME structure.
Internally, this required splitting the final closing delimiter out of
the various show_part functions and putting it into a new
show_part_end function instead. Also, the show_part function now
accepts a new "first" argument that is set not only for the first MIME
part of a message, but also for each first MIME part within a series
of multipart parts. This "first" argument controls the omission of a
preceding comma when printing a part (for json).
Many thanks to David Edmondson <dme@dme.org> for originally
identifying the lack of nesting in the json output and submitting an
early implementation of this feature. Thanks as well to Jameson Graef
Rollins <jrollins@finestructure.net> for carefully shepherding David's
patches through a remarkably long review process, patiently explaining
them, and providing a cleaned up series that led to this final
implementation. Jameson also provided the new emacs code here.
2011-05-18 00:34:57 +02:00
|
|
|
|
|
|
|
printf ("}");
|
|
|
|
}
|
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
static void
|
2011-05-26 03:01:13 +02:00
|
|
|
format_part_content_raw (GMimeObject *part)
|
2011-05-24 00:31:32 +02:00
|
|
|
{
|
2011-06-03 19:01:06 +02:00
|
|
|
if (! GMIME_IS_PART (part))
|
|
|
|
return;
|
|
|
|
|
2011-06-01 01:13:21 +02:00
|
|
|
GMimeStream *stream_stdout;
|
|
|
|
GMimeStream *stream_filter = NULL;
|
|
|
|
GMimeDataWrapper *wrapper;
|
|
|
|
|
|
|
|
stream_stdout = g_mime_stream_file_new (stdout);
|
2011-05-24 00:31:32 +02:00
|
|
|
g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
|
2011-06-01 01:13:21 +02:00
|
|
|
|
|
|
|
stream_filter = g_mime_stream_filter_new (stream_stdout);
|
|
|
|
|
|
|
|
wrapper = g_mime_part_get_content_object (GMIME_PART (part));
|
|
|
|
|
|
|
|
if (wrapper && stream_filter)
|
|
|
|
g_mime_data_wrapper_write_to_stream (wrapper, stream_filter);
|
|
|
|
|
|
|
|
if (stream_filter)
|
|
|
|
g_object_unref (stream_filter);
|
|
|
|
|
|
|
|
if (stream_stdout)
|
|
|
|
g_object_unref(stream_stdout);
|
2011-05-24 00:31:32 +02:00
|
|
|
}
|
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
static void
|
2011-05-20 20:45:33 +02:00
|
|
|
show_message (void *ctx,
|
|
|
|
const notmuch_show_format_t *format,
|
|
|
|
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-01-24 00:33:10 +01:00
|
|
|
if (format->part) {
|
|
|
|
void *local = talloc_new (ctx);
|
|
|
|
mime_node_t *root, *part;
|
|
|
|
|
|
|
|
if (mime_node_open (local, message, params->cryptoctx, params->decrypt,
|
|
|
|
&root) == NOTMUCH_STATUS_SUCCESS &&
|
|
|
|
(part = mime_node_seek_dfs (root, (params->part < 0 ?
|
|
|
|
0 : params->part))))
|
|
|
|
format->part (local, part, indent, params);
|
|
|
|
talloc_free (local);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
if (params->part <= 0) {
|
|
|
|
fputs (format->message_start, stdout);
|
|
|
|
if (format->message)
|
|
|
|
format->message(ctx, message, indent);
|
|
|
|
|
|
|
|
fputs (format->header_start, stdout);
|
|
|
|
if (format->header)
|
|
|
|
format->header(ctx, message);
|
|
|
|
fputs (format->header_end, stdout);
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
fputs (format->body_start, stdout);
|
|
|
|
}
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2011-05-26 03:01:13 +02:00
|
|
|
if (format->part_content)
|
2011-12-24 19:52:43 +01:00
|
|
|
show_message_body (message, format, params);
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
if (params->part <= 0) {
|
|
|
|
fputs (format->body_end, stdout);
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
fputs (format->message_end, stdout);
|
|
|
|
}
|
|
|
|
}
|
2009-11-16 05:39:25 +01:00
|
|
|
|
|
|
|
static void
|
2011-05-20 20:45:33 +02:00
|
|
|
show_messages (void *ctx,
|
|
|
|
const notmuch_show_format_t *format,
|
|
|
|
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;
|
2009-12-03 01:05:23 +01:00
|
|
|
notmuch_bool_t match;
|
2009-12-31 16:17:40 +01:00
|
|
|
int first_set = 1;
|
2009-12-03 01:05:23 +01:00
|
|
|
int next_indent;
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
fputs (format->message_set_start, stdout);
|
|
|
|
|
2009-11-16 05:39:25 +01:00
|
|
|
for (;
|
2010-03-09 18:22:29 +01:00
|
|
|
notmuch_messages_valid (messages);
|
|
|
|
notmuch_messages_move_to_next (messages))
|
2009-11-16 05:39:25 +01:00
|
|
|
{
|
2009-12-31 16:17:40 +01:00
|
|
|
if (!first_set)
|
|
|
|
fputs (format->message_set_sep, stdout);
|
|
|
|
first_set = 0;
|
|
|
|
|
|
|
|
fputs (format->message_set_start, stdout);
|
|
|
|
|
2009-11-16 05:39:25 +01:00
|
|
|
message = notmuch_messages_get (messages);
|
|
|
|
|
2009-12-03 01:05:23 +01:00
|
|
|
match = notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH);
|
2009-11-16 05:39:25 +01:00
|
|
|
|
2009-12-03 01:05:23 +01:00
|
|
|
next_indent = indent;
|
|
|
|
|
2011-05-21 00:01:52 +02:00
|
|
|
if (match || params->entire_thread) {
|
2011-05-24 00:31:32 +02:00
|
|
|
show_message (ctx, format, message, indent, params);
|
2009-12-03 01:05:23 +01:00
|
|
|
next_indent = indent + 1;
|
2009-12-31 16:17:40 +01:00
|
|
|
|
|
|
|
fputs (format->message_set_sep, stdout);
|
2009-12-03 01:05:23 +01:00
|
|
|
}
|
|
|
|
|
2011-05-21 00:01:52 +02:00
|
|
|
show_messages (ctx,
|
|
|
|
format,
|
|
|
|
notmuch_message_get_replies (message),
|
|
|
|
next_indent,
|
|
|
|
params);
|
2009-11-16 05:39:25 +01:00
|
|
|
|
|
|
|
notmuch_message_destroy (message);
|
2009-12-31 16:17:40 +01:00
|
|
|
|
|
|
|
fputs (format->message_set_end, stdout);
|
2009-11-16 05:39:25 +01:00
|
|
|
}
|
2009-12-31 16:17:40 +01:00
|
|
|
|
|
|
|
fputs (format->message_set_end, stdout);
|
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,
|
|
|
|
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;
|
2009-11-28 03:49:39 +01:00
|
|
|
|
2010-11-06 20:03:51 +01:00
|
|
|
if (notmuch_query_count_messages (query) != 1) {
|
|
|
|
fprintf (stderr, "Error: search term did not match precisely one message.\n");
|
|
|
|
return 1;
|
2009-11-28 03:49:39 +01:00
|
|
|
}
|
|
|
|
|
2010-11-06 20:03:51 +01:00
|
|
|
messages = notmuch_query_search_messages (query);
|
|
|
|
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);
|
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
/* Special case for --format=raw of full single message, just cat out file */
|
2011-05-24 00:31:32 +02:00
|
|
|
if (params->raw && 0 == params->part) {
|
2009-11-18 22:43:38 +01:00
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
const char *filename;
|
|
|
|
FILE *file;
|
|
|
|
size_t size;
|
|
|
|
char buf[4096];
|
2009-11-18 22:43:38 +01:00
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
filename = notmuch_message_get_filename (message);
|
|
|
|
if (filename == NULL) {
|
|
|
|
fprintf (stderr, "Error: Cannot message filename.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
2009-11-10 21:03:05 +01:00
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
file = fopen (filename, "r");
|
|
|
|
if (file == NULL) {
|
|
|
|
fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (!feof (file)) {
|
|
|
|
size = fread (buf, 1, sizeof (buf), file);
|
2012-01-19 23:29:18 +01:00
|
|
|
if (ferror (file)) {
|
|
|
|
fprintf (stderr, "Error: Read failed from %s\n", filename);
|
|
|
|
fclose (file);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fwrite (buf, size, 1, stdout) != 1) {
|
|
|
|
fprintf (stderr, "Error: Write failed\n");
|
|
|
|
fclose (file);
|
|
|
|
return 1;
|
|
|
|
}
|
2011-05-23 05:04:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fclose (file);
|
|
|
|
|
2011-05-24 00:31:32 +02:00
|
|
|
} else {
|
|
|
|
|
|
|
|
show_message (ctx, format, message, 0, params);
|
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
}
|
2010-11-06 20:03:51 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
/* Formatted output of threads */
|
2010-11-06 20:03:51 +01:00
|
|
|
static int
|
|
|
|
do_show (void *ctx,
|
|
|
|
notmuch_query_t *query,
|
2011-05-20 20:45:33 +02:00
|
|
|
const notmuch_show_format_t *format,
|
2011-05-21 00:01:52 +02:00
|
|
|
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;
|
|
|
|
int first_toplevel = 1;
|
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
fputs (format->message_set_start, stdout);
|
|
|
|
|
2009-12-03 01:05:23 +01:00
|
|
|
for (threads = notmuch_query_search_threads (query);
|
2010-03-09 18:22:29 +01:00
|
|
|
notmuch_threads_valid (threads);
|
|
|
|
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
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
if (!first_toplevel)
|
|
|
|
fputs (format->message_set_sep, stdout);
|
|
|
|
first_toplevel = 0;
|
|
|
|
|
2011-05-21 00:01:52 +02:00
|
|
|
show_messages (ctx, format, messages, 0, params);
|
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
|
|
|
}
|
|
|
|
|
2009-12-31 16:17:40 +01:00
|
|
|
fputs (format->message_set_end, stdout);
|
|
|
|
|
2009-11-12 05:29:30 +01:00
|
|
|
return 0;
|
2009-11-10 21:03:05 +01:00
|
|
|
}
|
2010-03-24 08:21:20 +01:00
|
|
|
|
2010-10-22 11:28:03 +02:00
|
|
|
int
|
2010-11-06 20:03:51 +01:00
|
|
|
notmuch_show_command (void *ctx, unused (int argc), unused (char *argv[]))
|
2010-10-22 11:28:03 +02:00
|
|
|
{
|
|
|
|
notmuch_config_t *config;
|
|
|
|
notmuch_database_t *notmuch;
|
|
|
|
notmuch_query_t *query;
|
|
|
|
char *query_string;
|
2010-11-06 20:03:51 +01:00
|
|
|
char *opt;
|
2011-05-20 20:45:33 +02:00
|
|
|
const notmuch_show_format_t *format = &format_text;
|
2011-05-21 00:01:52 +02:00
|
|
|
notmuch_show_params_t params;
|
2011-05-23 09:35:37 +02:00
|
|
|
int mbox = 0;
|
2011-05-24 00:38:49 +02:00
|
|
|
int format_specified = 0;
|
2010-10-22 11:28:03 +02:00
|
|
|
int i;
|
|
|
|
|
2011-05-21 00:01:52 +02:00
|
|
|
params.entire_thread = 0;
|
2011-05-23 05:04:02 +02:00
|
|
|
params.raw = 0;
|
2011-05-24 00:31:32 +02:00
|
|
|
params.part = -1;
|
2011-05-26 03:01:17 +02:00
|
|
|
params.cryptoctx = NULL;
|
2011-05-26 03:01:18 +02:00
|
|
|
params.decrypt = 0;
|
2011-05-21 00:01:52 +02:00
|
|
|
|
2011-10-21 14:19:17 +02:00
|
|
|
argc--; argv++; /* skip subcommand argument */
|
|
|
|
|
2010-10-22 11:28:03 +02:00
|
|
|
for (i = 0; i < argc && argv[i][0] == '-'; i++) {
|
2010-11-06 20:03:51 +01:00
|
|
|
if (strcmp (argv[i], "--") == 0) {
|
|
|
|
i++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (STRNCMP_LITERAL (argv[i], "--format=") == 0) {
|
|
|
|
opt = argv[i] + sizeof ("--format=") - 1;
|
|
|
|
if (strcmp (opt, "text") == 0) {
|
|
|
|
format = &format_text;
|
|
|
|
} else if (strcmp (opt, "json") == 0) {
|
|
|
|
format = &format_json;
|
2011-05-21 00:01:52 +02:00
|
|
|
params.entire_thread = 1;
|
2010-11-06 20:03:51 +01:00
|
|
|
} else if (strcmp (opt, "mbox") == 0) {
|
|
|
|
format = &format_mbox;
|
2011-05-23 09:35:37 +02:00
|
|
|
mbox = 1;
|
2010-11-06 20:03:51 +01:00
|
|
|
} else if (strcmp (opt, "raw") == 0) {
|
2011-05-24 00:31:32 +02:00
|
|
|
format = &format_raw;
|
2011-05-23 05:04:02 +02:00
|
|
|
params.raw = 1;
|
2010-11-06 20:03:51 +01:00
|
|
|
} else {
|
|
|
|
fprintf (stderr, "Invalid value for --format: %s\n", opt);
|
|
|
|
return 1;
|
|
|
|
}
|
2011-05-24 00:38:49 +02:00
|
|
|
format_specified = 1;
|
2011-05-24 00:31:32 +02:00
|
|
|
} else if (STRNCMP_LITERAL (argv[i], "--part=") == 0) {
|
|
|
|
params.part = atoi(argv[i] + sizeof ("--part=") - 1);
|
2010-11-06 20:03:51 +01:00
|
|
|
} else if (STRNCMP_LITERAL (argv[i], "--entire-thread") == 0) {
|
2011-05-21 00:01:52 +02:00
|
|
|
params.entire_thread = 1;
|
2011-05-26 03:01:18 +02:00
|
|
|
} else if ((STRNCMP_LITERAL (argv[i], "--verify") == 0) ||
|
|
|
|
(STRNCMP_LITERAL (argv[i], "--decrypt") == 0)) {
|
2011-05-26 03:01:17 +02:00
|
|
|
if (params.cryptoctx == NULL) {
|
2012-01-20 10:39:24 +01:00
|
|
|
#ifdef GMIME_ATLEAST_26
|
|
|
|
/* TODO: GMimePasswordRequestFunc */
|
|
|
|
if (NULL == (params.cryptoctx = g_mime_gpg_context_new(NULL, "gpg")))
|
|
|
|
#else
|
2011-06-04 01:57:46 +02:00
|
|
|
GMimeSession* session = g_object_new(g_mime_session_get_type(), NULL);
|
2011-05-26 03:01:17 +02:00
|
|
|
if (NULL == (params.cryptoctx = g_mime_gpg_context_new(session, "gpg")))
|
2012-01-20 10:39:24 +01:00
|
|
|
#endif
|
2011-05-26 03:01:17 +02:00
|
|
|
fprintf (stderr, "Failed to construct gpg context.\n");
|
|
|
|
else
|
|
|
|
g_mime_gpg_context_set_always_trust((GMimeGpgContext*)params.cryptoctx, FALSE);
|
2012-01-20 10:39:24 +01:00
|
|
|
#ifndef GMIME_ATLEAST_26
|
2011-05-26 03:01:17 +02:00
|
|
|
g_object_unref (session);
|
|
|
|
session = NULL;
|
2012-01-20 10:39:24 +01:00
|
|
|
#endif
|
2011-05-26 03:01:17 +02:00
|
|
|
}
|
2011-05-26 03:01:18 +02:00
|
|
|
if (STRNCMP_LITERAL (argv[i], "--decrypt") == 0)
|
|
|
|
params.decrypt = 1;
|
2010-11-06 20:03:51 +01:00
|
|
|
} else {
|
|
|
|
fprintf (stderr, "Unrecognized option: %s\n", argv[i]);
|
|
|
|
return 1;
|
|
|
|
}
|
2010-10-22 11:28:03 +02:00
|
|
|
}
|
|
|
|
|
2010-11-06 20:03:51 +01:00
|
|
|
argc -= i;
|
|
|
|
argv += i;
|
|
|
|
|
2010-10-22 11:28:03 +02:00
|
|
|
config = notmuch_config_open (ctx, NULL, NULL);
|
|
|
|
if (config == NULL)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
query_string = query_string_from_args (ctx, argc, argv);
|
|
|
|
if (query_string == NULL) {
|
|
|
|
fprintf (stderr, "Out of memory\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-05-23 09:35:37 +02:00
|
|
|
if (mbox && params.part > 0) {
|
|
|
|
fprintf (stderr, "Error: specifying parts is incompatible with mbox output format.\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
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");
|
2010-10-22 11:28:03 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
notmuch = notmuch_database_open (notmuch_config_get_database_path (config),
|
|
|
|
NOTMUCH_DATABASE_MODE_READ_ONLY);
|
|
|
|
if (notmuch == NULL)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
query = notmuch_query_create (notmuch, query_string);
|
|
|
|
if (query == NULL) {
|
2010-11-06 20:03:51 +01:00
|
|
|
fprintf (stderr, "Out of memory\n");
|
2010-10-22 11:28:03 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-05-24 00:38:49 +02:00
|
|
|
/* if part was requested and format was not specified, use format=raw */
|
|
|
|
if (params.part >= 0 && !format_specified)
|
|
|
|
format = &format_raw;
|
|
|
|
|
2011-05-23 05:04:02 +02:00
|
|
|
/* If --format=raw specified without specifying part, we can only
|
|
|
|
* output single message, so set part=0 */
|
2011-05-24 00:31:32 +02:00
|
|
|
if (params.raw && params.part < 0)
|
|
|
|
params.part = 0;
|
|
|
|
|
|
|
|
if (params.part >= 0)
|
2011-05-23 05:04:02 +02:00
|
|
|
return do_show_single (ctx, query, format, ¶ms);
|
2010-11-06 20:03:51 +01:00
|
|
|
else
|
2011-05-21 00:01:52 +02:00
|
|
|
return do_show (ctx, query, format, ¶ms);
|
2010-10-22 11:28:03 +02:00
|
|
|
|
|
|
|
notmuch_query_destroy (query);
|
|
|
|
notmuch_database_close (notmuch);
|
|
|
|
|
2011-05-26 03:01:17 +02:00
|
|
|
if (params.cryptoctx)
|
|
|
|
g_object_unref(params.cryptoctx);
|
|
|
|
|
2010-10-22 11:28:03 +02:00
|
|
|
return 0;
|
|
|
|
}
|