+}
+
+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");
+ }
+}
+
+static void
+format_part_start_json (unused (GMimeObject *part), int *part_count)
+{
+ printf ("{\"id\": %d", *part_count);
+}
+
+static void
+format_part_encstatus_json (int status)
+{
+ printf (", \"encstatus\": [{\"status\": ");
+ if (status) {
+ printf ("\"good\"");
+ } else {
+ printf ("\"bad\"");
+ }
+ printf ("}]");
+}
+
+#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
+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 */
+ printf ("\"status\": %s",
+ json_quote_str (ctx_quote,
+ signer_status_to_string (signer->status)));
+
+ 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) {
+ printf (", \"errors\": %d", signer->errors);
+ }
+
+ printf ("}");
+ signer = signer->next;
+ }
+
+ printf ("]");
+
+ talloc_free (ctx_quote);
+}
+#endif
+
+static void
+format_part_content_json (GMimeObject *part)
+{
+ GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+ GMimeStream *stream_memory = g_mime_stream_mem_new ();
+ const char *cid = g_mime_object_get_content_id (part);
+ void *ctx = talloc_new (NULL);
+ GByteArray *part_content;
+
+ printf (", \"content-type\": %s",
+ json_quote_str (ctx, g_mime_content_type_to_string (content_type)));
+
+ if (cid != NULL)
+ printf(", \"content-id\": %s", json_quote_str (ctx, cid));
+
+ if (GMIME_IS_PART (part))
+ {
+ const char *filename = g_mime_part_get_filename (GMIME_PART (part));
+ if (filename)
+ printf (", \"filename\": %s", json_quote_str (ctx, filename));
+ }
+
+ if (g_mime_content_type_is_type (content_type, "text", "*"))
+ {
+ /* 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.
+ * 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));
+
+ printf (", \"content\": %s", json_quote_chararray (ctx, (char *) part_content->data, part_content->len));
+ }
+ }
+ else if (g_mime_content_type_is_type (content_type, "multipart", "*"))
+ {
+ printf (", \"content\": [");
+ }
+ else if (g_mime_content_type_is_type (content_type, "message", "rfc822"))
+ {
+ printf (", \"content\": [{");
+ }
+
+ talloc_free (ctx);
+ if (stream_memory)
+ g_object_unref (stream_memory);
+}
+
+static void
+format_part_end_json (GMimeObject *part)
+{
+ GMimeContentType *content_type = g_mime_object_get_content_type (GMIME_OBJECT (part));
+
+ if (g_mime_content_type_is_type (content_type, "multipart", "*"))
+ printf ("]");
+ else if (g_mime_content_type_is_type (content_type, "message", "rfc822"))
+ printf ("}]");
+
+ printf ("}");
+}
+
+static void
+format_part_content_raw (GMimeObject *part)
+{
+ if (! GMIME_IS_PART (part))
+ return;
+
+ GMimeStream *stream_stdout;
+ GMimeStream *stream_filter = NULL;
+ GMimeDataWrapper *wrapper;
+
+ stream_stdout = g_mime_stream_file_new (stdout);
+ g_mime_stream_file_set_owner (GMIME_STREAM_FILE (stream_stdout), FALSE);
+
+ 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);
+}
+
+static void
+show_message (void *ctx,
+ const notmuch_show_format_t *format,
+ notmuch_message_t *message,
+ int indent,
+ notmuch_show_params_t *params)
+{
+ 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;
+ }
+
+ 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);
+
+ fputs (format->body_start, stdout);
+ }
+
+ if (format->part_content)
+ show_message_body (message, format, params);
+
+ if (params->part <= 0) {
+ fputs (format->body_end, stdout);
+
+ fputs (format->message_end, stdout);
+ }
+}
+
+static void
+show_messages (void *ctx,
+ const notmuch_show_format_t *format,
+ notmuch_messages_t *messages,
+ int indent,
+ notmuch_show_params_t *params)
+{
+ notmuch_message_t *message;
+ notmuch_bool_t match;
+ int first_set = 1;
+ int next_indent;
+
+ fputs (format->message_set_start, stdout);
+
+ for (;
+ notmuch_messages_valid (messages);
+ notmuch_messages_move_to_next (messages))
+ {
+ if (!first_set)
+ fputs (format->message_set_sep, stdout);
+ first_set = 0;