+ printf("%s%s", first ? "" : ",",
+ json_quote_str (ctx_quote, notmuch_tags_get (tags)));
+ first = 0;
+ }
+ printf("], ");
+ talloc_free (ctx_quote);
+}
+
+/* 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:
+ if (addresses)
+ g_object_unref (addresses);
+
+ 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;
+}
+
+void
+format_headers_json (sprinter_t *sp, GMimeMessage *message,
+ notmuch_bool_t reply)
+{
+ InternetAddressList *recipients;
+ const char *recipients_string;
+
+ sp->begin_map (sp);
+
+ sp->map_key (sp, "Subject");
+ sp->string (sp, g_mime_message_get_subject (message));
+
+ sp->map_key (sp, "From");
+ sp->string (sp, 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) {
+ sp->map_key (sp, "To");
+ sp->string (sp, 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) {
+ sp->map_key (sp, "Cc");
+ sp->string (sp, recipients_string);
+ }
+
+ if (reply) {
+ sp->map_key (sp, "In-reply-to");
+ sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "In-reply-to"));
+
+ sp->map_key (sp, "References");
+ sp->string (sp, g_mime_object_get_header (GMIME_OBJECT (message), "References"));
+ } else {
+ sp->map_key (sp, "Date");
+ sp->string (sp, g_mime_message_get_date_as_string (message));
+ }
+
+ sp->end (sp);
+}
+
+/* Write a MIME text part out to the given stream.
+ *
+ * If (flags & NOTMUCH_SHOW_TEXT_PART_REPLY), this prepends "> " to
+ * each output line.
+ *
+ * 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.
+ */
+void
+show_text_part_content (GMimeObject *part, GMimeStream *stream_out,
+ notmuch_show_text_part_flags flags)
+{
+ GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+ GMimeStream *stream_filter = NULL;
+ GMimeDataWrapper *wrapper;
+ const char *charset;
+
+ 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));
+
+ if (stream_out == NULL)
+ return;
+
+ 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. */
+ if (charset_filter) {
+ g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
+ charset_filter);
+ g_object_unref (charset_filter);
+ }
+
+ }
+
+ if (flags & NOTMUCH_SHOW_TEXT_PART_REPLY) {
+ GMimeFilter *reply_filter;
+ reply_filter = g_mime_filter_reply_new (TRUE);
+ if (reply_filter) {
+ g_mime_stream_filter_add (GMIME_STREAM_FILTER (stream_filter),
+ reply_filter);
+ g_object_unref (reply_filter);
+ }
+ }
+
+ 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);
+}
+
+#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
+static const char*
+signer_status_to_string (GMimeSignerStatus x)
+{
+ 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";
+}
+#endif
+
+#ifdef GMIME_ATLEAST_26
+static void
+format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+{
+ GMimeSignatureList *siglist = node->sig_list;
+
+ sp->begin_list (sp);
+
+ if (!siglist) {
+ sp->end (sp);
+ return;
+ }
+
+ int i;
+ for (i = 0; i < g_mime_signature_list_length (siglist); i++) {
+ GMimeSignature *signature = g_mime_signature_list_get_signature (siglist, i);
+
+ sp->begin_map (sp);
+
+ /* status */
+ GMimeSignatureStatus status = g_mime_signature_get_status (signature);
+ sp->map_key (sp, "status");
+ sp->string (sp, signature_status_to_string (status));
+
+ GMimeCertificate *certificate = g_mime_signature_get_certificate (signature);
+ if (status == GMIME_SIGNATURE_STATUS_GOOD) {
+ if (certificate) {
+ sp->map_key (sp, "fingerprint");
+ sp->string (sp, 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) {
+ sp->map_key (sp, "created");
+ sp->integer (sp, created);
+ }
+ time_t expires = g_mime_signature_get_expires (signature);
+ if (expires > 0) {
+ sp->map_key (sp, "expires");
+ sp->integer (sp, 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)) {
+ sp->map_key (sp, "userid");
+ sp->string (sp, name);
+ }
+ }
+ } else if (certificate) {
+ const char *key_id = g_mime_certificate_get_key_id (certificate);
+ if (key_id) {
+ sp->map_key (sp, "keyid");
+ sp->string (sp, key_id);
+ }
+ }
+
+ GMimeSignatureError errors = g_mime_signature_get_errors (signature);
+ if (errors != GMIME_SIGNATURE_ERROR_NONE) {
+ sp->map_key (sp, "errors");
+ sp->integer (sp, errors);
+ }
+
+ sp->end (sp);
+ }
+
+ sp->end (sp);
+}
+#else
+static void
+format_part_sigstatus_json (sprinter_t *sp, mime_node_t *node)
+{
+ const GMimeSignatureValidity* validity = node->sig_validity;
+
+ sp->begin_list (sp);
+
+ if (!validity) {
+ sp->end (sp);
+ return;
+ }
+
+ const GMimeSigner *signer = g_mime_signature_validity_get_signers (validity);
+ while (signer) {
+ sp->begin_map (sp);
+
+ /* status */
+ sp->map_key (sp, "status");
+ sp->string (sp, signer_status_to_string (signer->status));
+
+ if (signer->status == GMIME_SIGNER_STATUS_GOOD)
+ {
+ if (signer->fingerprint) {
+ sp->map_key (sp, "fingerprint");
+ sp->string (sp, signer->fingerprint);
+ }
+ /* these dates are seconds since the epoch; should we
+ * provide a more human-readable format string? */
+ if (signer->created) {
+ sp->map_key (sp, "created");
+ sp->integer (sp, signer->created);
+ }
+ if (signer->expires) {
+ sp->map_key (sp, "expires");
+ sp->integer (sp, 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)) {
+ sp->map_key (sp, "userid");
+ sp->string (sp, signer->name);
+ }
+ }
+ } else {
+ if (signer->keyid) {
+ sp->map_key (sp, "keyid");
+ sp->string (sp, signer->keyid);
+ }
+ }
+ if (signer->errors != GMIME_SIGNER_ERROR_NONE) {
+ sp->map_key (sp, "errors");
+ sp->integer (sp, signer->errors);
+ }
+
+ sp->end (sp);
+ signer = signer->next;
+ }
+
+ sp->end (sp);
+}
+#endif
+
+static notmuch_status_t
+format_part_text (const void *ctx, sprinter_t *sp, mime_node_t *node,
+ int indent, const notmuch_show_params_t *params)
+{
+ /* The disposition and content-type metadata are associated with
+ * the envelope for message parts */
+ GMimeObject *meta = node->envelope_part ?
+ GMIME_OBJECT (node->envelope_part) : node->part;
+ GMimeContentType *content_type = g_mime_object_get_content_type (meta);
+ const notmuch_bool_t leaf = GMIME_IS_PART (node->part);
+ const char *part_type;
+ int i;
+
+ if (node->envelope_file) {
+ notmuch_message_t *message = node->envelope_file;
+
+ part_type = "message";
+ printf ("\f%s{ id:%s depth:%d match:%d excluded:%d filename:%s\n",
+ part_type,
+ notmuch_message_get_message_id (message),
+ indent,
+ notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_MATCH) ? 1 : 0,
+ notmuch_message_get_flag (message, NOTMUCH_MESSAGE_FLAG_EXCLUDED) ? 1 : 0,
+ notmuch_message_get_filename (message));
+ } else {
+ GMimeContentDisposition *disposition = g_mime_object_get_content_disposition (meta);
+ const char *cid = g_mime_object_get_content_id (meta);
+ const char *filename = leaf ?
+ g_mime_part_get_filename (GMIME_PART (node->part)) : NULL;
+
+ if (disposition &&
+ strcmp (disposition->disposition, GMIME_DISPOSITION_ATTACHMENT) == 0)
+ part_type = "attachment";
+ else
+ part_type = "part";
+
+ printf ("\f%s{ ID: %d", part_type, node->part_num);
+ if (filename)
+ printf (", Filename: %s", filename);
+ if (cid)
+ printf (", Content-id: %s", cid);
+ printf (", Content-type: %s\n", g_mime_content_type_to_string (content_type));
+ }
+
+ if (GMIME_IS_MESSAGE (node->part)) {
+ GMimeMessage *message = GMIME_MESSAGE (node->part);
+ InternetAddressList *recipients;
+ const char *recipients_string;
+
+ printf ("\fheader{\n");
+ if (node->envelope_file)
+ printf ("%s\n", _get_one_line_summary (ctx, node->envelope_file));
+ printf ("Subject: %s\n", g_mime_message_get_subject (message));
+ 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));
+ printf ("\fheader}\n");
+
+ printf ("\fbody{\n");
+ }
+
+ if (leaf) {
+ if (g_mime_content_type_is_type (content_type, "text", "*") &&
+ !g_mime_content_type_is_type (content_type, "text", "html"))
+ {
+ GMimeStream *stream_stdout = g_mime_stream_file_new (stdout);
+ g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
+ show_text_part_content (node->part, stream_stdout, 0);
+ g_object_unref(stream_stdout);
+ } else {
+ printf ("Non-text part: %s\n",
+ g_mime_content_type_to_string (content_type));
+ }
+ }
+
+ for (i = 0; i < node->nchildren; i++)
+ format_part_text (ctx, sp, mime_node_child (node, i), indent, params);
+
+ if (GMIME_IS_MESSAGE (node->part))
+ printf ("\fbody}\n");
+
+ printf ("\f%s}\n", part_type);