/* 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; FILE *file; const char *from; time_t date; struct tm date_gmtime; char date_asctime[26]; char *line = NULL; size_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 = fopen (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 = getline (&line, &line_size, file)) != -1 ) { if (_is_from_line (line)) putchar ('>'); printf ("%s", line); } printf ("\n"); fclose (file); return NOTMUCH_STATUS_SUCCESS; }
static int msg_header(Tcl_Interp *interp, notmuch_message_t *msg, int argc, const char *argv[]) { if (argc != 1) { tcl_result_printf(interp, "expected: msg header <header>"); return TCL_ERROR; } const char *value = notmuch_message_get_header(msg, argv[0]); if (value == NULL) { return TCL_ERROR; } tcl_result_printf(interp, "%s", value); return TCL_OK; }
/* Get a nice, single-line summary of message. */ static const char * _get_one_line_summary (const void *ctx, notmuch_message_t *message) { const char *from; time_t date; const char *relative_date; const char *tags; from = notmuch_message_get_header (message, "from"); date = notmuch_message_get_date (message); relative_date = notmuch_time_relative_date (ctx, date); tags = _get_tags_as_string (ctx, message); return talloc_asprintf (ctx, "%s (%s) (%s)", from, relative_date, tags); }
static void format_headers_text (const void *ctx, notmuch_message_t *message) { const char *headers[] = { "Subject", "From", "To", "Cc", "Bcc", "Date" }; const char *name, *value; unsigned int i; printf ("%s\n", _get_one_line_summary (ctx, message)); for (i = 0; i < ARRAY_SIZE (headers); i++) { name = headers[i]; value = notmuch_message_get_header (message, name); if (value && strlen (value)) printf ("%s: %s\n", name, value); } }
/* Print a message in "mboxrd" format as documented, for example, * here: * * http://qmail.org/qmail-manual-html/man5/mbox.html */ static void format_message_mbox (const void *ctx, notmuch_message_t *message, unused (int indent)) { const char *filename; FILE *file; const char *from; time_t date; struct tm date_gmtime; char date_asctime[26]; char *line = NULL; size_t line_size; ssize_t line_len; filename = notmuch_message_get_filename (message); file = fopen (filename, "r"); if (file == NULL) { fprintf (stderr, "Failed to open %s: %s\n", filename, strerror (errno)); return; } 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 = getline (&line, &line_size, file)) != -1 ) { if (_is_from_line (line)) putchar ('>'); printf ("%s", line); } printf ("\n"); fclose (file); }
/* * Try to find user's email address in one of the extra To-like * headers: Envelope-To, X-Original-To, and Delivered-To (searched in * that order). * * Return the address that was found, if any, and NULL otherwise. */ static const char * get_from_in_to_headers (notmuch_config_t *config, notmuch_message_t *message) { size_t i; const char *tohdr, *addr; const char *to_headers[] = { "Envelope-to", "X-Original-To", "Delivered-To", }; for (i = 0; i < ARRAY_SIZE (to_headers); i++) { tohdr = notmuch_message_get_header (message, to_headers[i]); /* Note: tohdr potentially contains a list of email addresses. */ addr = user_address_in_string (tohdr, config); if (addr) return addr; } return NULL; }
/* * Get the concatenated Received: headers and search from the front * (last Received: header added) and try to extract from them * indications to which email address this message was delivered. * * The Received: header is special in our get_header function and is * always concatenated. * * Return the address that was found, if any, and NULL otherwise. */ static const char * guess_from_in_received_headers (notmuch_config_t *config, notmuch_message_t *message) { const char *received, *addr; char *sanitized; received = notmuch_message_get_header (message, "received"); if (! received) return NULL; sanitized = sanitize_string (NULL, received); if (! sanitized) return NULL; addr = guess_from_in_received_for (config, sanitized); if (! addr) addr = guess_from_in_received_by (config, sanitized); talloc_free (sanitized); return addr; }
static GMimeMessage * create_reply_message(void *ctx, notmuch_config_t *config, notmuch_message_t *message, notmuch_bool_t reply_all) { const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; /* The 1 means we want headers in a "pretty" order. */ GMimeMessage *reply = g_mime_message_new (1); if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return NULL; } subject = notmuch_message_get_header (message, "subject"); if (subject) { if (strncasecmp (subject, "Re:", 3)) subject = talloc_asprintf (ctx, "Re: %s", subject); g_mime_message_set_subject (reply, subject); } from_addr = add_recipients_from_message (reply, config, message, reply_all); /* * Sadly, there is no standard way to find out to which email * address a mail was delivered - what is in the headers depends * on the MTAs used along the way. * * If none of the user's email addresses are in the To: or Cc: * headers, we try a number of heuristics which hopefully will * answer this question. * * First, check for Envelope-To:, X-Original-To:, and * Delivered-To: headers. */ if (from_addr == NULL) from_addr = get_from_in_to_headers (config, message); /* * Check for a (for <*****@*****.**>) clause in Received: headers, * and the domain part of known email addresses in the 'by' part * of Received: headers */ if (from_addr == NULL) from_addr = guess_from_in_received_headers (config, message); /* Default to user's primary address. */ if (from_addr == NULL) from_addr = notmuch_config_get_user_primary_email (config); from_addr = talloc_asprintf (ctx, "%s <%s>", notmuch_config_get_user_name (config), from_addr); g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr); in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to); orig_references = notmuch_message_get_header (message, "references"); if (!orig_references) /* Treat errors like missing References headers. */ orig_references = ""; references = talloc_asprintf (ctx, "%s%s%s", *orig_references ? orig_references : "", *orig_references ? " " : "", in_reply_to); g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); return reply; }
/* Augment the recipients of 'reply' from the "Reply-to:", "From:", * "To:", "Cc:", and "Bcc:" headers of 'message'. * * If 'reply_all' is true, use sender and all recipients, otherwise * scan the headers for the first that contains something other than * the user's addresses and add the recipients from this header * (typically this would be reply-to-sender, but also handles reply to * user's own message in a sensible way). * * If any of the user's addresses were found in these headers, the * first of these returned, otherwise NULL is returned. */ static const char * add_recipients_from_message (GMimeMessage *reply, notmuch_config_t *config, notmuch_message_t *message, notmuch_bool_t reply_all) { struct { const char *header; const char *fallback; GMimeRecipientType recipient_type; } reply_to_map[] = { { "reply-to", "from", GMIME_RECIPIENT_TYPE_TO }, { "to", NULL, GMIME_RECIPIENT_TYPE_TO }, { "cc", NULL, GMIME_RECIPIENT_TYPE_CC }, { "bcc", NULL, GMIME_RECIPIENT_TYPE_BCC } }; const char *from_addr = NULL; unsigned int i; unsigned int n = 0; /* Some mailing lists munge the Reply-To header despite it being A Bad * Thing, see http://www.unicom.com/pw/reply-to-harmful.html * * The munging is easy to detect, because it results in a * redundant reply-to header, (with an address that already exists * in either To or Cc). So in this case, we ignore the Reply-To * field and use the From header. This ensures the original sender * will get the reply even if not subscribed to the list. Note * that the address in the Reply-To header will always appear in * the reply. */ if (reply_to_header_is_redundant (message)) { reply_to_map[0].header = "from"; reply_to_map[0].fallback = NULL; } for (i = 0; i < ARRAY_SIZE (reply_to_map); i++) { const char *recipients; recipients = notmuch_message_get_header (message, reply_to_map[i].header); if ((recipients == NULL || recipients[0] == '\0') && reply_to_map[i].fallback) recipients = notmuch_message_get_header (message, reply_to_map[i].fallback); n += scan_address_string (recipients, config, reply, reply_to_map[i].recipient_type, &from_addr); if (!reply_all && n) { /* Stop adding new recipients in reply-to-sender mode if * we have added some recipient(s) above. * * This also handles the case of user replying to his own * message, where reply-to/from is not a recipient. In * this case there may be more than one recipient even if * not replying to all. */ reply = NULL; /* From address and some recipients are enough, bail out. */ if (from_addr) break; } } return from_addr; }
/* This format is currently tuned for a git send-email --notmuch hook */ static int notmuch_reply_format_headers_only(void *ctx, notmuch_config_t *config, notmuch_query_t *query, unused (notmuch_show_params_t *params), notmuch_bool_t reply_all, unused (sprinter_t *sp)) { GMimeMessage *reply; notmuch_messages_t *messages; notmuch_message_t *message; const char *in_reply_to, *orig_references, *references; char *reply_headers; notmuch_status_t status; status = notmuch_query_search_messages_st (query, &messages); if (print_status_query ("notmuch reply", query, status)) return 1; for (; notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); /* The 0 means we do not want headers in a "pretty" order. */ reply = g_mime_message_new (0); if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return 1; } in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to); orig_references = notmuch_message_get_header (message, "references"); /* We print In-Reply-To followed by References because git format-patch treats them * specially. Git does not interpret the other headers specially */ references = talloc_asprintf (ctx, "%s%s%s", orig_references ? orig_references : "", orig_references ? " " : "", in_reply_to); g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); (void)add_recipients_from_message (reply, config, message, reply_all); reply_headers = g_mime_object_to_string (GMIME_OBJECT (reply)); printf ("%s", reply_headers); free (reply_headers); g_object_unref (G_OBJECT (reply)); reply = NULL; notmuch_message_destroy (message); } return 0; }
static int notmuch_reply_format_default(void *ctx, notmuch_config_t *config, notmuch_query_t *query, notmuch_show_params_t *params) { GMimeMessage *reply; notmuch_messages_t *messages; notmuch_message_t *message; const char *subject, *from_addr = NULL; const char *in_reply_to, *orig_references, *references; const notmuch_show_format_t *format = &format_reply; for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); /* The 1 means we want headers in a "pretty" order. */ reply = g_mime_message_new (1); if (reply == NULL) { fprintf (stderr, "Out of memory\n"); return 1; } subject = notmuch_message_get_header (message, "subject"); if (subject) { if (strncasecmp (subject, "Re:", 3)) subject = talloc_asprintf (ctx, "Re: %s", subject); g_mime_message_set_subject (reply, subject); } from_addr = add_recipients_from_message (reply, config, message); if (from_addr == NULL) from_addr = guess_from_received_header (config, message); if (from_addr == NULL) from_addr = notmuch_config_get_user_primary_email (config); from_addr = talloc_asprintf (ctx, "%s <%s>", notmuch_config_get_user_name (config), from_addr); g_mime_object_set_header (GMIME_OBJECT (reply), "From", from_addr); in_reply_to = talloc_asprintf (ctx, "<%s>", notmuch_message_get_message_id (message)); g_mime_object_set_header (GMIME_OBJECT (reply), "In-Reply-To", in_reply_to); orig_references = notmuch_message_get_header (message, "references"); references = talloc_asprintf (ctx, "%s%s%s", orig_references ? orig_references : "", orig_references ? " " : "", in_reply_to); g_mime_object_set_header (GMIME_OBJECT (reply), "References", references); show_reply_headers (reply); g_object_unref (G_OBJECT (reply)); reply = NULL; printf ("On %s, %s wrote:\n", notmuch_message_get_header (message, "date"), notmuch_message_get_header (message, "from")); show_message_body (notmuch_message_get_filename (message), format, params); notmuch_message_destroy (message); } return 0; }
static const char * guess_from_received_header (notmuch_config_t *config, notmuch_message_t *message) { const char *received,*primary,*by; const char **other; char *tohdr; char *mta,*ptr,*token; char *domain=NULL; char *tld=NULL; const char *delim=". \t"; size_t i,j,other_len; const char *to_headers[] = {"Envelope-to", "X-Original-To"}; primary = notmuch_config_get_user_primary_email (config); other = notmuch_config_get_user_other_email (config, &other_len); /* sadly, there is no standard way to find out to which email * address a mail was delivered - what is in the headers depends * on the MTAs used along the way. So we are trying a number of * heuristics which hopefully will answer this question. * We only got here if none of the users email addresses are in * the To: or Cc: header. From here we try the following in order: * 1) check for an Envelope-to: header * 2) check for an X-Original-To: header * 3) check for a (for <*****@*****.**>) clause in Received: headers * 4) check for the domain part of known email addresses in the * 'by' part of Received headers * If none of these work, we give up and return NULL */ for (i = 0; i < sizeof(to_headers)/sizeof(*to_headers); i++) { tohdr = xstrdup(notmuch_message_get_header (message, to_headers[i])); if (tohdr && *tohdr) { /* tohdr is potentialy a list of email addresses, so here we * check if one of the email addresses is a substring of tohdr */ if (strcasestr(tohdr, primary)) { free(tohdr); return primary; } for (j = 0; j < other_len; j++) if (strcasestr (tohdr, other[j])) { free(tohdr); return other[j]; } free(tohdr); } } /* We get the concatenated Received: headers and search from the * front (last Received: header added) and try to extract from * them indications to which email address this message was * delivered. * The Received: header is special in our get_header function * and is always concatenated. */ received = notmuch_message_get_header (message, "received"); if (received == NULL) return NULL; /* First we look for a " for <*****@*****.**>" in the received * header */ ptr = strstr (received, " for "); if (ptr) { /* the text following is potentialy a list of email addresses, * so again we check if one of the email addresses is a * substring of ptr */ if (strcasestr(ptr, primary)) { return primary; } for (i = 0; i < other_len; i++) if (strcasestr (ptr, other[i])) { return other[i]; } } /* Finally, we parse all the " by MTA ..." headers to guess the * email address that this was originally delivered to. * We extract just the MTA here by removing leading whitespace and * assuming that the MTA name ends at the next whitespace. * We test for *(by+4) to be non-'\0' to make sure there's * something there at all - and then assume that the first * whitespace delimited token that follows is the receiving * system in this step of the receive chain */ by = received; while((by = strstr (by, " by ")) != NULL) { by += 4; if (*by == '\0') break; mta = xstrdup (by); token = strtok(mta," \t"); if (token == NULL) { free (mta); break; } /* Now extract the last two components of the MTA host name * as domain and tld. */ domain = tld = NULL; while ((ptr = strsep (&token, delim)) != NULL) { if (*ptr == '\0') continue; domain = tld; tld = ptr; } if (domain) { /* Recombine domain and tld and look for it among the configured * email addresses. * This time we have a known domain name and nothing else - so * the test is the other way around: we check if this is a * substring of one of the email addresses. */ *(tld-1) = '.'; if (strcasestr(primary, domain)) { free(mta); return primary; } for (i = 0; i < other_len; i++) if (strcasestr (other[i],domain)) { free(mta); return other[i]; } } free (mta); } return NULL; }
static int do_search_messages (search_context_t *ctx) { notmuch_message_t *message; notmuch_messages_t *messages; notmuch_filenames_t *filenames; sprinter_t *format = ctx->format; int i; notmuch_status_t status; if (ctx->offset < 0) { unsigned count; notmuch_status_t status; status = notmuch_query_count_messages_st (ctx->query, &count); if (print_status_query ("notmuch search", ctx->query, status)) return 1; ctx->offset += count; if (ctx->offset < 0) ctx->offset = 0; } status = notmuch_query_search_messages_st (ctx->query, &messages); if (print_status_query ("notmuch search", ctx->query, status)) return 1; format->begin_list (format); for (i = 0; notmuch_messages_valid (messages) && (ctx->limit < 0 || i < ctx->offset + ctx->limit); notmuch_messages_move_to_next (messages), i++) { if (i < ctx->offset) continue; message = notmuch_messages_get (messages); if (ctx->output == OUTPUT_FILES) { int j; filenames = notmuch_message_get_filenames (message); for (j = 1; notmuch_filenames_valid (filenames); notmuch_filenames_move_to_next (filenames), j++) { if (ctx->dupe < 0 || ctx->dupe == j) { format->string (format, notmuch_filenames_get (filenames)); format->separator (format); } } notmuch_filenames_destroy( filenames ); } else if (ctx->output == OUTPUT_MESSAGES) { /* special case 1 for speed */ if (ctx->dupe <= 1 || ctx->dupe <= _count_filenames (message)) { format->set_prefix (format, "id"); format->string (format, notmuch_message_get_message_id (message)); format->separator (format); } } else { if (ctx->output & OUTPUT_SENDER) { const char *addrs; addrs = notmuch_message_get_header (message, "from"); process_address_header (ctx, addrs); } if (ctx->output & OUTPUT_RECIPIENTS) { const char *hdrs[] = { "to", "cc", "bcc" }; const char *addrs; size_t j; for (j = 0; j < ARRAY_SIZE (hdrs); j++) { addrs = notmuch_message_get_header (message, hdrs[j]); process_address_header (ctx, addrs); } } } notmuch_message_destroy (message); } if (ctx->addresses && (ctx->output & OUTPUT_COUNT || ctx->dedup == DEDUP_ADDRESS)) g_hash_table_foreach (ctx->addresses, print_hash_value, ctx); notmuch_messages_destroy (messages); format->end (format); return 0; }
int main (int argc, char** argv) { GFile *db_dir, *db_file; notmuch_status_t status; notmuch_database_t *db = NULL; notmuch_query_t *query = NULL; notmuch_messages_t *messages = NULL; notmuch_message_t *message = NULL; GMainLoop *loop = NULL; const char *query_string = "date:2014-02-01.."; if (argc != 2) { g_warning ("Usage: %s EVOLUTION_MAILDIR", argv[0]); return 1; } db_dir = g_file_new_for_path (argv[1]); db_file = g_file_get_child (db_dir, ".notmuch"); if (!g_file_query_exists (db_dir, NULL)) { g_object_unref (db_dir); g_object_unref (db_file); g_error ("directory %s does not exists"); return 2; } if (!g_file_query_exists (db_file, NULL)) status = notmuch_database_create (argv[1], &db); else status = notmuch_database_open (argv[1], NOTMUCH_DATABASE_MODE_READ_WRITE, &db); if (status) { g_error ("Could not open database: %d", status); g_object_unref (db_dir); g_object_unref (db_file); notmuch_database_destroy (db); return 3; } scan_directory (db, db_dir); //loop = g_main_loop_new (NULL, FALSE); //g_main_loop_run (loop); query = notmuch_query_create (db, query_string); if (!query) { g_error ("Could not create query from string = \"%s\"", query_string); notmuch_database_destroy (db); g_object_unref (db_file); g_object_unref (db_dir); return 4; } g_message ("Query results -\n\n"); for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) { message = notmuch_messages_get (messages); g_message ("Message file: %s", notmuch_message_get_filename (message)); g_message ("Message ID: %s", notmuch_message_get_message_id (message)); g_message ("Message Sender: %s", notmuch_message_get_header (message, "from")); g_message ("Message Recipients: %s", notmuch_message_get_header (message, "to")); g_message ("Message Subject: %s", notmuch_message_get_header (message, "subject")); g_message ("Message date: %s\n", notmuch_message_get_header (message, "date")); notmuch_message_destroy (message); } notmuch_query_destroy (query); notmuch_database_destroy (db); g_object_unref (db_file); g_object_unref (db_dir); return 0; }