notmuch_message_get_header only returns the first instance of the specified
header in a message.
notmuch_message_get_concat_header concatenates the values from ALL instances
of that header in a message. This is useful for example to get the full
delivery path as captured in all of the Received: headers.

Signed-off-by: Dirk Hohndel <hohndel at infradead.org>
---
 lib/database.cc       |   14 +++++++-------
 lib/message-file.c    |   49 +++++++++++++++++++++++++++++++++++--------------
 lib/message.cc        |   12 +++++++++++-
 lib/notmuch-private.h |    2 +-
 lib/notmuch.h         |   16 ++++++++++++++++
 5 files changed, 70 insertions(+), 23 deletions(-)

diff --git a/lib/database.cc b/lib/database.cc
index 6842faf..d706263 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -1289,11 +1289,11 @@ _notmuch_database_link_message_to_parents 
(notmuch_database_t *notmuch,
     parents = g_hash_table_new_full (g_str_hash, g_str_equal,
                                     _my_talloc_free_for_g_hash, NULL);

-    refs = notmuch_message_file_get_header (message_file, "references");
+    refs = notmuch_message_file_get_header (message_file, "references", 0);
     parse_references (message, notmuch_message_get_message_id (message),
                      parents, refs);

-    in_reply_to = notmuch_message_file_get_header (message_file, 
"in-reply-to");
+    in_reply_to = notmuch_message_file_get_header (message_file, 
"in-reply-to", 0);
     parse_references (message, notmuch_message_get_message_id (message),
                      parents, in_reply_to);

@@ -1506,9 +1506,9 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
         * let's make sure that what we're looking at looks like an
         * actual email message.
         */
-       from = notmuch_message_file_get_header (message_file, "from");
-       subject = notmuch_message_file_get_header (message_file, "subject");
-       to = notmuch_message_file_get_header (message_file, "to");
+       from = notmuch_message_file_get_header (message_file, "from", 0);
+       subject = notmuch_message_file_get_header (message_file, "subject", 0);
+       to = notmuch_message_file_get_header (message_file, "to", 0);

        if ((from == NULL || *from == '\0') &&
            (subject == NULL || *subject == '\0') &&
@@ -1521,7 +1521,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
        /* Now that we're sure it's mail, the first order of business
         * is to find a message ID (or else create one ourselves). */

-       header = notmuch_message_file_get_header (message_file, "message-id");
+       header = notmuch_message_file_get_header (message_file, "message-id", 
0);
        if (header && *header != '\0') {
            message_id = _parse_message_id (message_file, header, NULL);

@@ -1580,7 +1580,7 @@ notmuch_database_add_message (notmuch_database_t *notmuch,
            if (ret)
                goto DONE;

-           date = notmuch_message_file_get_header (message_file, "date");
+           date = notmuch_message_file_get_header (message_file, "date", 0);
            _notmuch_message_set_date (message, date);

            _notmuch_message_index_file (message, filename);
diff --git a/lib/message-file.c b/lib/message-file.c
index 0c152a3..a01adbb 100644
--- a/lib/message-file.c
+++ b/lib/message-file.c
@@ -209,15 +209,21 @@ copy_header_unfolding (header_value_closure_t *value,

 /* As a special-case, a value of NULL for header_desired will force
  * the entire header to be parsed if it is not parsed already. This is
- * used by the _notmuch_message_file_get_headers_end function. */
+ * used by the _notmuch_message_file_get_headers_end function. 
+ * If concat is 'true' then it parses the whole message and
+ * concatenates all instances of the header in question. This is
+ * currently used to get a complete Received: header when analyzing
+ * the path the mail has taken from sender to recipient.
+ */
 const char *
 notmuch_message_file_get_header (notmuch_message_file_t *message,
-                                const char *header_desired)
+                                const char *header_desired,
+                                int concat)
 {
     int contains;
-    char *header, *decoded_value;
+    char *header, *decoded_value, *header_sofar, *combined_header;
     const char *s, *colon;
-    int match;
+    int match, newhdr, hdrsofar;
     static int initialized = 0;

     if (! initialized) {
@@ -227,7 +233,7 @@ notmuch_message_file_get_header (notmuch_message_file_t 
*message,

     message->parsing_started = 1;

-    if (header_desired == NULL)
+    if (concat || header_desired == NULL) 
        contains = 0;
     else
        contains = g_hash_table_lookup_extended (message->headers,
@@ -237,6 +243,9 @@ notmuch_message_file_get_header (notmuch_message_file_t 
*message,
     if (contains && decoded_value)
        return decoded_value;

+    if (concat)
+       message->parsing_finished = 0;
+
     if (message->parsing_finished)
        return "";

@@ -312,20 +321,32 @@ notmuch_message_file_get_header (notmuch_message_file_t 
*message,

        NEXT_HEADER_LINE (&message->value);

-       if (header_desired == 0)
+       if (concat || header_desired == NULL)
            match = 0;
        else
            match = (strcasecmp (header, header_desired) == 0);

        decoded_value = g_mime_utils_header_decode_text (message->value.str);
-       if (g_hash_table_lookup (message->headers, header) == NULL) {
-           /* Only insert if we don't have a value for this header, yet.
-            * This way we always return the FIRST instance of any header
-            * we search for
-            * FIXME: we should be returning ALL instances of a header
-            *        or at least provide a way to iterate over them
-            */
-           g_hash_table_insert (message->headers, header, decoded_value);
+       header_sofar = (char *)g_hash_table_lookup (message->headers, header);
+       if (concat) {
+           if (header_sofar == NULL) {
+               /* Only insert if we don't have a value for this header, yet. */
+               g_hash_table_insert (message->headers, header, decoded_value);
+           } else {
+               /* the caller wants them all concatenated */
+               newhdr = strlen(decoded_value);
+               hdrsofar = strlen(header_sofar);
+               combined_header = xmalloc(hdrsofar + newhdr + 2);
+               strncpy(combined_header,header_sofar,hdrsofar);
+               *(combined_header+hdrsofar) = ' ';
+               strncpy(combined_header+hdrsofar+1,decoded_value,newhdr+1);
+               g_hash_table_insert (message->headers, header, combined_header);
+           }
+       } else {
+           if (header_sofar == NULL) {
+               /* Only insert if we don't have a value for this header, yet. */
+               g_hash_table_insert (message->headers, header, decoded_value);
+           }
        }
        if (match)
            return decoded_value;
diff --git a/lib/message.cc b/lib/message.cc
index 721c9a6..fb8fe95 100644
--- a/lib/message.cc
+++ b/lib/message.cc
@@ -264,7 +264,17 @@ notmuch_message_get_header (notmuch_message_t *message, 
const char *header)
     if (message->message_file == NULL)
        return NULL;

-    return notmuch_message_file_get_header (message->message_file, header);
+    return notmuch_message_file_get_header (message->message_file, header, 0);
+}
+
+const char *
+notmuch_message_get_concat_header (notmuch_message_t *message, const char 
*header)
+{
+    _notmuch_message_ensure_message_file (message);
+    if (message->message_file == NULL)
+       return NULL;
+
+    return notmuch_message_file_get_header (message->message_file, header, 1);
 }

 /* Return the message ID from the In-Reply-To header of 'message'.
diff --git a/lib/notmuch-private.h b/lib/notmuch-private.h
index d52d84d..9f8a10a 100644
--- a/lib/notmuch-private.h
+++ b/lib/notmuch-private.h
@@ -342,7 +342,7 @@ notmuch_message_file_restrict_headersv 
(notmuch_message_file_t *message,
  */
 const char *
 notmuch_message_file_get_header (notmuch_message_file_t *message,
-                                const char *header);
+                                const char *header, int concat);

 /* messages.c */

diff --git a/lib/notmuch.h b/lib/notmuch.h
index a7e66dd..d77eb5c 100644
--- a/lib/notmuch.h
+++ b/lib/notmuch.h
@@ -787,6 +787,22 @@ notmuch_message_get_date  (notmuch_message_t *message);
 const char *
 notmuch_message_get_header (notmuch_message_t *message, const char *header);

+/* Get the concatenated value of all instances of the specified header
+ * from 'message'.
+ *
+ * The value will be read from the actual message file, not from the
+ * notmuch database. The header name is case insensitive.
+ *
+ * The returned string belongs to the message so should not be
+ * modified or freed by the caller (nor should it be referenced after
+ * the message is destroyed).
+ *
+ * Returns an empty string ("") if the message does not contain a
+ * header line matching 'header'. Returns NULL if any error occurs.
+ */
+const char *
+notmuch_message_get_concat_header (notmuch_message_t *message, const char 
*header);
+
 /* Get the tags for 'message', returning a notmuch_tags_t object which
  * can be used to iterate over all tags.
  *
-- 
1.6.6.1

Reply via email to