[PATCH v4 2/4] Introduce a generic tree-like abstraction for MIME traversal.
authorAustin Clements <amdragon@MIT.EDU>
Sat, 24 Dec 2011 03:45:46 +0000 (22:45 +1900)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 17:41:14 +0000 (09:41 -0800)
7f/7a781f47a5164731fb8049ac0cf643e4923b05 [new file with mode: 0644]

diff --git a/7f/7a781f47a5164731fb8049ac0cf643e4923b05 b/7f/7a781f47a5164731fb8049ac0cf643e4923b05
new file mode 100644 (file)
index 0000000..428c2bf
--- /dev/null
@@ -0,0 +1,447 @@
+Return-Path: <amthrax@drake.mit.edu>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+       by olra.theworths.org (Postfix) with ESMTP id 74768429E3F\r
+       for <notmuch@notmuchmail.org>; Fri, 23 Dec 2011 19:46:05 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -0.7\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
+       tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+       by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+       with ESMTP id ot5vCocRr1R8 for <notmuch@notmuchmail.org>;\r
+       Fri, 23 Dec 2011 19:46:04 -0800 (PST)\r
+Received: from dmz-mailsec-scanner-5.mit.edu (DMZ-MAILSEC-SCANNER-5.MIT.EDU\r
+       [18.7.68.34])\r
+       by olra.theworths.org (Postfix) with ESMTP id F3BA7429E30\r
+       for <notmuch@notmuchmail.org>; Fri, 23 Dec 2011 19:46:03 -0800 (PST)\r
+X-AuditID: 12074422-b7fd66d0000008f9-a5-4ef54afb93a9\r
+Received: from mailhub-auth-4.mit.edu ( [18.7.62.39])\r
+       by dmz-mailsec-scanner-5.mit.edu (Symantec Messaging Gateway) with SMTP\r
+       id 24.61.02297.BFA45FE4; Fri, 23 Dec 2011 22:46:03 -0500 (EST)\r
+Received: from outgoing.mit.edu (OUTGOING-AUTH.MIT.EDU [18.7.22.103])\r
+       by mailhub-auth-4.mit.edu (8.13.8/8.9.2) with ESMTP id pBO3k31Y015217; \r
+       Fri, 23 Dec 2011 22:46:03 -0500\r
+Received: from drake.mit.edu (c-76-21-105-205.hsd1.ca.comcast.net\r
+       [76.21.105.205]) (authenticated bits=0)\r
+       (User authenticated as amdragon@ATHENA.MIT.EDU)\r
+       by outgoing.mit.edu (8.13.6/8.12.4) with ESMTP id pBO3k1hW017815\r
+       (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
+       Fri, 23 Dec 2011 22:46:02 -0500 (EST)\r
+Received: from amthrax by drake.mit.edu with local (Exim 4.77)\r
+       (envelope-from <amthrax@drake.mit.edu>)\r
+       id 1ReIYb-0007IE-1J; Fri, 23 Dec 2011 22:46:01 -0500\r
+From: Austin Clements <amdragon@MIT.EDU>\r
+To: notmuch@notmuchmail.org\r
+Subject:\r
+ [PATCH v4 2/4] Introduce a generic tree-like abstraction for MIME traversal.\r
+Date: Fri, 23 Dec 2011 22:45:46 -0500\r
+Message-Id: <1324698348-27620-3-git-send-email-amdragon@mit.edu>\r
+X-Mailer: git-send-email 1.7.7.3\r
+In-Reply-To: <1324698348-27620-1-git-send-email-amdragon@mit.edu>\r
+References: <1323460468-4030-1-git-send-email-amdragon@mit.edu>\r
+       <1324698348-27620-1-git-send-email-amdragon@mit.edu>\r
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=UTF-8\r
+Content-Transfer-Encoding: 8bit\r
+X-Brightmail-Tracker:\r
+ H4sIAAAAAAAAA+NgFmphleLIzCtJLcpLzFFi42IRYrdT1/3t9dXPYOU6OYvrN2cyOzB6PFt1\r
+       izmAMYrLJiU1J7MstUjfLoEr49j9HewFvakVP+6dY2lg3O/fxcjJISFgIrHm8E12CFtM4sK9\r
+       9WxdjFwcQgL7GCWmrf4C5WxglLg+6SM7hHOOSeLaoiNMEM58Ronbey8zg/SzCWhIbNu/nBHE\r
+       FhGQlth5dzZrFyMHB7OAmsSfLhWQsLBAtcSniTOZQGwWAVWJN8uvg5XzCjhIbP59gRXiDAWJ\r
+       c6vPgZ3EKeAosXXre0aQMUICZRLNZ4IgygUlTs58wgIxXV1i/TwhkDCzgLxE89bZzBMYhWYh\r
+       qZqFUDULSdUCRuZVjLIpuVW6uYmZOcWpybrFyYl5ealFuqZ6uZkleqkppZsYwQHsorSD8edB\r
+       pUOMAhyMSjy8zUu/+AmxJpYVV+YeYpTkYFIS5b3q9tVPiC8pP6UyI7E4I76oNCe1+BCjBAez\r
+       kgivZhJQOW9KYmVValE+TEqag0VJnFdd652fkEB6YklqdmpqQWoRTFaGg0NJglcBGKlCgkWp\r
+       6akVaZk5JQhpJg5OkOE8QMO5QWp4iwsSc4sz0yHypxh1Oa5u3XCWUYglLz8vVUqcdxlIkQBI\r
+       UUZpHtwcWOJ5xSgO9JYwrxRIFQ8wacFNegW0hAloSYwRyAfFJYkIKakGxjkxaqH2b/pP3c8+\r
+       zunb8CZuud6EyAl63jt9TZQeOQtUCq2pDwo/3iB+S+9sct0d75I/nxg8Z/LkiJ77lKUv//bE\r
+       +ahjF+T8s/48uh1XIq9dlprL4zx1zozXNcz2XY/mxh69ef0rNwO/3DvvjCQzJb9jq06Je5/d\r
+       17PzqvnzS1cdw3sXeDKZKLEUZyQaajEXFScCANH72QsXAwAA\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+       <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+       <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Sat, 24 Dec 2011 03:46:05 -0000\r
+\r
+This wraps all of the complex MIME part handling in a single, simple\r
+function that gets part N from *any* MIME object, so traversing a MIME\r
+part tree becomes a two-line for loop.  Furthermore, the MIME node\r
+structure provides easy access to envelopes for message parts as well\r
+as cryptographic information.\r
+\r
+This code is directly derived from the current show_message_body code\r
+(much of it is identical), but the control relation is inverted:\r
+instead of show_message_body controlling the traversal of the MIME\r
+structure and invoking callbacks, the caller controls the traversal of\r
+the MIME structure.\r
+---\r
+ Makefile.local   |    1 +\r
+ mime-node.c      |  238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\r
+ notmuch-client.h |   83 +++++++++++++++++++\r
+ 3 files changed, 322 insertions(+), 0 deletions(-)\r
+ create mode 100644 mime-node.c\r
+\r
+diff --git a/Makefile.local b/Makefile.local\r
+index 97f397f..516f26e 100644\r
+--- a/Makefile.local\r
++++ b/Makefile.local\r
+@@ -315,6 +315,7 @@ notmuch_client_srcs =              \\r
+       notmuch-time.c          \\r
+       query-string.c          \\r
+       show-message.c          \\r
++      mime-node.c             \\r
+       json.c\r
\r
+ notmuch_client_modules = $(notmuch_client_srcs:.c=.o)\r
+diff --git a/mime-node.c b/mime-node.c\r
+new file mode 100644\r
+index 0000000..fd8e754\r
+--- /dev/null\r
++++ b/mime-node.c\r
+@@ -0,0 +1,238 @@\r
++/* notmuch - Not much of an email program, (just index and search)\r
++ *\r
++ * Copyright © 2009 Carl Worth\r
++ * Copyright © 2009 Keith Packard\r
++ *\r
++ * This program is free software: you can redistribute it and/or modify\r
++ * it under the terms of the GNU General Public License as published by\r
++ * the Free Software Foundation, either version 3 of the License, or\r
++ * (at your option) any later version.\r
++ *\r
++ * This program is distributed in the hope that it will be useful,\r
++ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
++ * GNU General Public License for more details.\r
++ *\r
++ * You should have received a copy of the GNU General Public License\r
++ * along with this program.  If not, see http://www.gnu.org/licenses/ .\r
++ *\r
++ * Authors: Carl Worth <cworth@cworth.org>\r
++ *          Keith Packard <keithp@keithp.com>\r
++ *          Austin Clements <aclements@csail.mit.edu>\r
++ */\r
++\r
++#include "notmuch-client.h"\r
++\r
++/* Context that gets inherited from the root node. */\r
++typedef struct mime_node_context {\r
++    /* Per-message resources.  These are allocated internally and must\r
++     * be destroyed. */\r
++    FILE *file;\r
++    GMimeStream *stream;\r
++    GMimeParser *parser;\r
++    GMimeMessage *mime_message;\r
++\r
++    /* Context provided by the caller. */\r
++    GMimeCipherContext *cryptoctx;\r
++    notmuch_bool_t decrypt;\r
++} mime_node_context_t;\r
++\r
++static int\r
++_mime_node_context_free (mime_node_context_t *res)\r
++{\r
++    if (res->mime_message)\r
++      g_object_unref (res->mime_message);\r
++\r
++    if (res->parser)\r
++      g_object_unref (res->parser);\r
++\r
++    if (res->stream)\r
++      g_object_unref (res->stream);\r
++\r
++    if (res->file)\r
++      fclose (res->file);\r
++\r
++    return 0;\r
++}\r
++\r
++notmuch_status_t\r
++mime_node_open (const void *ctx, notmuch_message_t *message,\r
++              GMimeCipherContext *cryptoctx, notmuch_bool_t decrypt,\r
++              mime_node_t **root_out)\r
++{\r
++    const char *filename = notmuch_message_get_filename (message);\r
++    mime_node_context_t *mctx;\r
++    mime_node_t *root;\r
++    notmuch_status_t status;\r
++\r
++    root = talloc_zero (ctx, mime_node_t);\r
++    if (root == NULL) {\r
++      fprintf (stderr, "Out of memory.\n");\r
++      status = NOTMUCH_STATUS_OUT_OF_MEMORY;\r
++      goto DONE;\r
++    }\r
++\r
++    /* Create the tree-wide context */\r
++    mctx = talloc_zero (root, mime_node_context_t);\r
++    if (mctx == NULL) {\r
++      fprintf (stderr, "Out of memory.\n");\r
++      status = NOTMUCH_STATUS_OUT_OF_MEMORY;\r
++      goto DONE;\r
++    }\r
++    talloc_set_destructor (mctx, _mime_node_context_free);\r
++\r
++    mctx->file = fopen (filename, "r");\r
++    if (! mctx->file) {\r
++      fprintf (stderr, "Error opening %s: %s\n", filename, strerror (errno));\r
++      status = NOTMUCH_STATUS_FILE_ERROR;\r
++      goto DONE;\r
++    }\r
++\r
++    mctx->stream = g_mime_stream_file_new (mctx->file);\r
++    g_mime_stream_file_set_owner (GMIME_STREAM_FILE (mctx->stream), FALSE);\r
++\r
++    mctx->parser = g_mime_parser_new_with_stream (mctx->stream);\r
++\r
++    mctx->mime_message = g_mime_parser_construct_message (mctx->parser);\r
++\r
++    mctx->cryptoctx = cryptoctx;\r
++    mctx->decrypt = decrypt;\r
++\r
++    /* Create the root node */\r
++    root->part = GMIME_OBJECT (mctx->mime_message);\r
++    root->envelope_file = message;\r
++    root->nchildren = 1;\r
++    root->ctx = mctx;\r
++\r
++    *root_out = root;\r
++    return NOTMUCH_STATUS_SUCCESS;\r
++\r
++DONE:\r
++    talloc_free (root);\r
++    return status;\r
++}\r
++\r
++static int\r
++_signature_validity_free (GMimeSignatureValidity **proxy)\r
++{\r
++    g_mime_signature_validity_free (*proxy);\r
++    return 0;\r
++}\r
++\r
++static mime_node_t *\r
++_mime_node_create (const mime_node_t *parent, GMimeObject *part)\r
++{\r
++    mime_node_t *node = talloc_zero (parent, mime_node_t);\r
++    GError *err = NULL;\r
++\r
++    /* Set basic node properties */\r
++    node->part = part;\r
++    node->ctx = parent->ctx;\r
++    if (!talloc_reference (node, node->ctx)) {\r
++      fprintf (stderr, "Out of memory.\n");\r
++      talloc_free (node);\r
++      return NULL;\r
++    }\r
++\r
++    /* Deal with the different types of parts */\r
++    if (GMIME_IS_PART (part)) {\r
++      node->nchildren = 0;\r
++    } else if (GMIME_IS_MULTIPART (part)) {\r
++      node->nchildren = g_mime_multipart_get_count (GMIME_MULTIPART (part));\r
++    } else if (GMIME_IS_MESSAGE_PART (part)) {\r
++      /* Promote part to an envelope and open it */\r
++      GMimeMessagePart *message_part = GMIME_MESSAGE_PART (part);\r
++      GMimeMessage *message = g_mime_message_part_get_message (message_part);\r
++      node->envelope_part = message_part;\r
++      node->part = GMIME_OBJECT (message);\r
++      node->nchildren = 1;\r
++    } else {\r
++      fprintf (stderr, "Warning: Unknown mime part type: %s.\n",\r
++               g_type_name (G_OBJECT_TYPE (part)));\r
++      talloc_free (node);\r
++      return NULL;\r
++    }\r
++\r
++    /* Handle PGP/MIME parts */\r
++    if (GMIME_IS_MULTIPART_ENCRYPTED (part)\r
++      && node->ctx->cryptoctx && node->ctx->decrypt) {\r
++      if (node->nchildren != 2) {\r
++          /* this violates RFC 3156 section 4, so we won't bother with it. */\r
++          fprintf (stderr, "Error: %d part(s) for a multipart/encrypted "\r
++                   "message (must be exactly 2)\n",\r
++                   node->nchildren);\r
++      } else {\r
++          GMimeMultipartEncrypted *encrypteddata =\r
++              GMIME_MULTIPART_ENCRYPTED (part);\r
++          node->decrypt_attempted = TRUE;\r
++          node->decrypted_child = g_mime_multipart_encrypted_decrypt\r
++              (encrypteddata, node->ctx->cryptoctx, &err);\r
++          if (node->decrypted_child) {\r
++              node->decrypt_success = node->sig_attempted = TRUE;\r
++              node->sig_validity = g_mime_multipart_encrypted_get_signature_validity (encrypteddata);\r
++          } else {\r
++              fprintf (stderr, "Failed to decrypt part: %s\n",\r
++                       (err ? err->message : "no error explanation given"));\r
++          }\r
++      }\r
++    } else if (GMIME_IS_MULTIPART_SIGNED (part) && node->ctx->cryptoctx) {\r
++      if (node->nchildren != 2) {\r
++          /* this violates RFC 3156 section 5, so we won't bother with it. */\r
++          fprintf (stderr, "Error: %d part(s) for a multipart/signed message "\r
++                   "(must be exactly 2)\n",\r
++                   node->nchildren);\r
++      } else {\r
++          /* For some reason the GMimeSignatureValidity returned\r
++           * here is not a const (inconsistent with that\r
++           * returned by\r
++           * g_mime_multipart_encrypted_get_signature_validity,\r
++           * and therefore needs to be properly disposed of.\r
++           *\r
++           * In GMime 2.6, they're both non-const, so we'll be able\r
++           * to clean up this asymmetry. */\r
++          GMimeSignatureValidity *sig_validity = g_mime_multipart_signed_verify\r
++              (GMIME_MULTIPART_SIGNED (part), node->ctx->cryptoctx, &err);\r
++          node->sig_attempted = TRUE;\r
++          node->sig_validity = sig_validity;\r
++          if (sig_validity) {\r
++              GMimeSignatureValidity **proxy =\r
++                  talloc (node, GMimeSignatureValidity *);\r
++              *proxy = sig_validity;\r
++              talloc_set_destructor (proxy, _signature_validity_free);\r
++          }\r
++      }\r
++    }\r
++\r
++    if (node->sig_attempted && !node->sig_validity)\r
++      fprintf (stderr, "Failed to verify signed part: %s\n",\r
++               (err ? err->message : "no error explanation given"));\r
++\r
++    if (err)\r
++      g_error_free (err);\r
++\r
++    return node;\r
++}\r
++\r
++mime_node_t *\r
++mime_node_child (const mime_node_t *parent, int child)\r
++{\r
++    GMimeObject *sub;\r
++\r
++    if (!parent || child < 0 || child >= parent->nchildren)\r
++      return NULL;\r
++\r
++    if (GMIME_IS_MULTIPART (parent->part)) {\r
++      if (child == 1 && parent->decrypted_child)\r
++          sub = parent->decrypted_child;\r
++      else\r
++          sub = g_mime_multipart_get_part\r
++              (GMIME_MULTIPART (parent->part), child);\r
++    } else if (GMIME_IS_MESSAGE (parent->part)) {\r
++      sub = g_mime_message_get_mime_part (GMIME_MESSAGE (parent->part));\r
++    } else {\r
++      /* This should have been caught by message_part_create */\r
++      INTERNAL_ERROR ("Unexpected GMimeObject type: %s",\r
++                      g_type_name (G_OBJECT_TYPE (parent->part)));\r
++    }\r
++    return _mime_node_create (parent, sub);\r
++}\r
+diff --git a/notmuch-client.h b/notmuch-client.h\r
+index c521efa..e23b51c 100644\r
+--- a/notmuch-client.h\r
++++ b/notmuch-client.h\r
+@@ -241,5 +241,88 @@ notmuch_run_hook (const char *db_path, const char *hook);\r
+ notmuch_bool_t\r
+ debugger_is_active (void);\r
\r
++/* mime-node.c */\r
++\r
++/* mime_node_t represents a single node in a MIME tree.  A MIME tree\r
++ * abstracts the different ways of traversing different types of MIME\r
++ * parts, allowing a MIME message to be viewed as a generic tree of\r
++ * parts.  Message-type parts have one child, multipart-type parts\r
++ * have multiple children, and leaf parts have zero children.\r
++ */\r
++typedef struct mime_node {\r
++    /* The MIME object of this part.  This will be a GMimeMessage,\r
++     * GMimePart, GMimeMultipart, or a subclass of one of these.\r
++     *\r
++     * This will never be a GMimeMessagePart because GMimeMessagePart\r
++     * is structurally redundant with GMimeMessage.  If this part is a\r
++     * message (that is, 'part' is a GMimeMessage), then either\r
++     * envelope_file will be set to a notmuch_message_t (for top-level\r
++     * messages) or envelope_part will be set to a GMimeMessagePart\r
++     * (for embedded message parts).\r
++     */\r
++    GMimeObject *part;\r
++\r
++    /* If part is a GMimeMessage, these record the envelope of the\r
++     * message: either a notmuch_message_t representing a top-level\r
++     * message, or a GMimeMessagePart representing a MIME part\r
++     * containing a message.\r
++     */\r
++    notmuch_message_t *envelope_file;\r
++    GMimeMessagePart *envelope_part;\r
++\r
++    /* The number of children of this part. */\r
++    int nchildren;\r
++\r
++    /* True if decryption of this part was attempted. */\r
++    notmuch_bool_t decrypt_attempted;\r
++    /* True if decryption of this part's child succeeded.  In this\r
++     * case, the decrypted part is substituted for the second child of\r
++     * this part (which would usually be the encrypted data). */\r
++    notmuch_bool_t decrypt_success;\r
++\r
++    /* True if signature verification on this part was attempted. */\r
++    notmuch_bool_t sig_attempted;\r
++    /* For signed or encrypted containers, the validity of the\r
++     * signature.  May be NULL if signature verification failed.  If\r
++     * there are simply no signatures, this will be non-NULL with an\r
++     * empty signers list. */\r
++    const GMimeSignatureValidity *sig_validity;\r
++\r
++    /* Internal: Context inherited from the root iterator. */\r
++    struct mime_node_context *ctx;\r
++\r
++    /* Internal: For successfully decrypted multipart parts, the\r
++     * decrypted part to substitute for the second child. */\r
++    GMimeObject *decrypted_child;\r
++} mime_node_t;\r
++\r
++/* Construct a new MIME node pointing to the root message part of\r
++ * message.  If cryptoctx is non-NULL, it will be used to verify\r
++ * signatures on any child parts.  If decrypt is true, then cryptoctx\r
++ * will additionally be used to decrypt any encrypted child parts.\r
++ *\r
++ * Return value:\r
++ *\r
++ * NOTMUCH_STATUS_SUCCESS: Root node is returned in *node_out.\r
++ *\r
++ * NOTMUCH_STATUS_FILE_ERROR: Failed to open message file.\r
++ *\r
++ * NOTMUCH_STATUS_OUT_OF_MEMORY: Out of memory.\r
++ */\r
++notmuch_status_t\r
++mime_node_open (const void *ctx, notmuch_message_t *message,\r
++              GMimeCipherContext *cryptoctx, notmuch_bool_t decrypt,\r
++              mime_node_t **node_out);\r
++\r
++/* Return a new MIME node for the requested child part of parent.\r
++ * parent will be used as the talloc context for the returned child\r
++ * node.\r
++ *\r
++ * In case of any failure, this function returns NULL, (after printing\r
++ * an error message on stderr).\r
++ */\r
++mime_node_t *\r
++mime_node_child (const mime_node_t *parent, int child);\r
++\r
+ #include "command-line-arguments.h"\r
+ #endif\r
+-- \r
+1.7.7.3\r
+\r