+format_omitted_part_meta_sprinter (sprinter_t *sp, GMimeObject *meta, GMimePart *part)
+{
+ const char *content_charset = g_mime_object_get_content_type_parameter (meta, "charset");
+ const char *cte = g_mime_object_get_header (meta, "content-transfer-encoding");
+ GMimeDataWrapper *wrapper = g_mime_part_get_content (part);
+ GMimeStream *stream = g_mime_data_wrapper_get_stream (wrapper);
+ ssize_t content_length = g_mime_stream_length (stream);
+
+ if (content_charset != NULL) {
+ sp->map_key (sp, "content-charset");
+ sp->string (sp, content_charset);
+ }
+ 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);
+ }
+}
+
+void
+format_part_sprinter (const void *ctx, sprinter_t *sp, mime_node_t *node,
+ int duplicate,
+ bool output_body,
+ bool include_html)
+{
+ /* Any changes to the JSON or S-Expression format should be
+ * reflected in the file devel/schemata. */
+
+ if (node->envelope_file) {
+ const _notmuch_message_crypto_t *msg_crypto = NULL;
+ sp->begin_map (sp);
+ format_message_sprinter (sp, node->envelope_file);
+
+ sp->map_key (sp, "duplicate");
+ sp->integer (sp, duplicate > 0 ? duplicate : 1);
+
+ if (output_body) {
+ sp->map_key (sp, "body");
+ sp->begin_list (sp);
+ format_part_sprinter (ctx, sp, mime_node_child (node, 0), -1, true, include_html);
+ sp->end (sp);
+ }
+
+ msg_crypto = mime_node_get_message_crypto_status (node);
+ 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);
+ }
+ if (msg_crypto->payload_subject) {
+ sp->map_key (sp, "headers");
+ sp->begin_list (sp);
+ sp->string (sp, "Subject");
+ sp->end (sp);
+ }
+ 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");
+
+ 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);
+ }
+ }
+ sp->end (sp);
+ }
+ }
+ sp->end (sp);
+ }
+
+ sp->map_key (sp, "headers");
+ format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false, msg_crypto);
+
+ sp->end (sp);
+ return;
+ }
+
+ /* 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);
+ char *content_string;
+ const char *disposition = _get_disposition (meta);
+ const char *cid = g_mime_object_get_content_id (meta);
+ const char *filename = GMIME_IS_PART (node->part) ? (
+ g_mime_part_get_filename (GMIME_PART (node->part) ) ) : NULL;
+ int nclose = 0;
+ int i;
+
+ sp->begin_map (sp);
+
+ sp->map_key (sp, "id");
+ sp->integer (sp, node->part_num);
+
+ 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);
+ }
+
+ if (node->verify_attempted) {
+ sp->map_key (sp, "sigstatus");
+ format_part_sigstatus_sprinter (sp, node->sig_list);
+ }
+
+ sp->map_key (sp, "content-type");
+ content_string = g_mime_content_type_get_mime_type (content_type);
+ sp->string (sp, content_string);
+ g_free (content_string);
+
+ if (disposition) {
+ sp->map_key (sp, "content-disposition");
+ sp->string (sp, disposition);
+ }
+
+ if (cid) {
+ sp->map_key (sp, "content-id");
+ sp->string (sp, cid);
+ }
+
+ if (filename) {
+ sp->map_key (sp, "filename");
+ sp->string (sp, filename);
+ }
+
+ if (GMIME_IS_PART (node->part)) {
+ /* 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 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
+ * report the charset for text/html parts.
+ */
+ if (g_mime_content_type_is_type (content_type, "text", "*") &&
+ (include_html ||
+ ! g_mime_content_type_is_type (content_type, "text", "html"))) {
+ GMimeStream *stream_memory = g_mime_stream_mem_new ();
+ GByteArray *part_content;
+ show_text_part_content (node->part, stream_memory, 0);
+ part_content = g_mime_stream_mem_get_byte_array (GMIME_STREAM_MEM (stream_memory));
+ sp->map_key (sp, "content");
+ sp->string_len (sp, (char *) part_content->data, part_content->len);
+ g_object_unref (stream_memory);
+ } else {
+ /* 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));
+ }
+ } else if (GMIME_IS_MULTIPART (node->part)) {
+ sp->map_key (sp, "content");
+ sp->begin_list (sp);
+ nclose = 1;
+ } else if (GMIME_IS_MESSAGE (node->part)) {
+ sp->map_key (sp, "content");
+ sp->begin_list (sp);
+ sp->begin_map (sp);
+
+ sp->map_key (sp, "headers");
+ format_headers_sprinter (sp, GMIME_MESSAGE (node->part), false, NULL);
+
+ sp->map_key (sp, "body");
+ sp->begin_list (sp);
+ nclose = 3;
+ }
+
+ for (i = 0; i < node->nchildren; i++)
+ format_part_sprinter (ctx, sp, mime_node_child (node, i), -1, true, include_html);
+
+ /* Close content structures */
+ for (i = 0; i < nclose; i++)
+ sp->end (sp);
+ /* Close part map */
+ sp->end (sp);
+}
+
+static notmuch_status_t
+format_part_sprinter_entry (const void *ctx, sprinter_t *sp,
+ mime_node_t *node, unused (int indent),
+ const notmuch_show_params_t *params)
+{
+ format_part_sprinter (ctx, sp, node, params->duplicate, params->output_body,
+ params->include_html);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+/* Print a message in "mboxrd" format as documented, for example,
+ * here:
+ *
+ * http://qmail.org/qmail-manual-html/man5/mbox.html
+ */
+static notmuch_status_t
+format_part_mbox (const void *ctx, unused (sprinter_t *sp), mime_node_t *node,
+ unused (int indent),
+ unused (const notmuch_show_params_t *params))
+{
+ notmuch_message_t *message = node->envelope_file;
+
+ const char *filename;
+ gzFile file;
+ const char *from;
+
+ time_t date;
+ struct tm date_gmtime;
+ char date_asctime[26];
+
+ char *line = NULL;
+ ssize_t line_size;
+ ssize_t line_len;
+
+ if (! message)
+ INTERNAL_ERROR ("format_part_mbox requires a root part");
+
+ filename = notmuch_message_get_filename (message);
+ file = gzopen (filename, "r");
+ if (file == NULL) {
+ fprintf (stderr, "Failed to open %s: %s\n",
+ filename, strerror (errno));
+ return NOTMUCH_STATUS_FILE_ERROR;
+ }
+
+ 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 = gz_getline (message, &line, &line_size, file)) != UTIL_EOF ) {
+ if (_is_from_line (line))
+ putchar ('>');
+ printf ("%s", line);
+ }
+
+ printf ("\n");
+
+ gzclose (file);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+format_part_raw (unused (const void *ctx), unused (sprinter_t *sp),
+ mime_node_t *node, unused (int indent),
+ const notmuch_show_params_t *params)
+{
+ if (node->envelope_file) {
+ /* Special case the entire message to avoid MIME parsing. */
+ const char *filename;
+ GMimeStream *stream = NULL;
+ ssize_t ssize;
+ char buf[4096];
+ notmuch_status_t ret = NOTMUCH_STATUS_FILE_ERROR;
+
+ filename = _get_filename (node->envelope_file, params->duplicate);
+ if (filename == NULL) {
+ fprintf (stderr, "Error: Cannot get message filename.\n");
+ goto DONE;
+ }
+
+ stream = g_mime_stream_gzfile_open (filename);
+ if (stream == NULL) {
+ fprintf (stderr, "Error: Cannot open file %s: %s\n", filename, strerror (errno));
+ goto DONE;
+ }
+
+ while (! g_mime_stream_eos (stream)) {
+ ssize = g_mime_stream_read (stream, buf, sizeof (buf));
+ if (ssize < 0) {
+ fprintf (stderr, "Error: Read failed from %s\n", filename);
+ goto DONE;
+ }
+
+ if (ssize > 0 && fwrite (buf, ssize, 1, stdout) != 1) {
+ fprintf (stderr, "Error: Write %zd chars to stdout failed\n", ssize);
+ goto DONE;
+ }
+ }
+
+ ret = NOTMUCH_STATUS_SUCCESS;
+
+ /* XXX This DONE is just for the special case of a node in a single file */
+ DONE:
+ if (stream)
+ g_object_unref (stream);
+
+ return ret;
+ }
+
+ GMimeStream *stream_filter = g_mime_stream_filter_new (params->out_stream);
+
+ if (GMIME_IS_PART (node->part)) {
+ /* For leaf parts, we emit only the transfer-decoded
+ * body. */
+ GMimeDataWrapper *wrapper;
+ wrapper = g_mime_part_get_content (GMIME_PART (node->part));
+
+ 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)
+ g_mime_object_write_to_stream (node->part, NULL, stream_filter);
+ }
+
+ if (stream_filter)
+ g_object_unref (stream_filter);
+
+ return NOTMUCH_STATUS_SUCCESS;
+}
+
+static notmuch_status_t
+show_message (void *ctx,
+ const notmuch_show_format_t *format,
+ sprinter_t *sp,
+ notmuch_message_t *message,
+ int indent,
+ notmuch_show_params_t *params)
+{
+ void *local = talloc_new (ctx);
+ mime_node_t *root, *part;
+ notmuch_status_t status;
+ 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);
+
+ status = mime_node_open (local, message, params->duplicate, &(params->crypto), &root);
+ if (status)
+ goto DONE;
+ part = mime_node_seek_dfs (root, (params->part < 0 ? 0 : params->part));
+ if (part)
+ status = format->part (local, sp, part, indent, params);
+ 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));
+ }
+ }
+ }
+ DONE:
+ talloc_free (local);
+ return status;
+}
+
+static notmuch_status_t
+show_messages (void *ctx,
+ const notmuch_show_format_t *format,
+ sprinter_t *sp,
+ notmuch_messages_t *messages,
+ int indent,
+ notmuch_show_params_t *params)