+ _config_set (config, &config->user_primary_email, "user", "primary_email", primary_email);
+}
+
+const char **
+notmuch_config_get_user_other_email (notmuch_config_t *config, size_t *length)
+{
+ return _config_get_list (config, "user", "other_email",
+ &(config->user_other_email),
+ &(config->user_other_email_length), length);
+}
+
+const char **
+notmuch_config_get_new_tags (notmuch_config_t *config, size_t *length)
+{
+ return _config_get_list (config, "new", "tags",
+ &(config->new_tags),
+ &(config->new_tags_length), length);
+}
+
+const char **
+notmuch_config_get_new_ignore (notmuch_config_t *config, size_t *length)
+{
+ return _config_get_list (config, "new", "ignore",
+ &(config->new_ignore),
+ &(config->new_ignore_length), length);
+}
+
+void
+notmuch_config_set_user_other_email (notmuch_config_t *config,
+ const char *list[],
+ size_t length)
+{
+ _config_set_list (config, "user", "other_email", list, length,
+ &(config->user_other_email));
+}
+
+void
+notmuch_config_set_new_tags (notmuch_config_t *config,
+ const char *list[],
+ size_t length)
+{
+ _config_set_list (config, "new", "tags", list, length,
+ &(config->new_tags));
+}
+
+void
+notmuch_config_set_new_ignore (notmuch_config_t *config,
+ const char *list[],
+ size_t length)
+{
+ _config_set_list (config, "new", "ignore", list, length,
+ &(config->new_ignore));
+}
+
+const char **
+notmuch_config_get_search_exclude_tags (notmuch_config_t *config, size_t *length)
+{
+ return _config_get_list (config, "search", "exclude_tags",
+ &(config->search_exclude_tags),
+ &(config->search_exclude_tags_length), length);
+}
+
+void
+notmuch_config_set_search_exclude_tags (notmuch_config_t *config,
+ const char *list[],
+ size_t length)
+{
+ _config_set_list (config, "search", "exclude_tags", list, length,
+ &(config->search_exclude_tags));
+}
+
+
+/* Given a configuration item of the form <group>.<key> return the
+ * component group and key. If any error occurs, print a message on
+ * stderr and return 1. Otherwise, return 0.
+ *
+ * Note: This function modifies the original 'item' string.
+ */
+static int
+_item_split (char *item, char **group, char **key)
+{
+ char *period;
+
+ *group = item;
+
+ period = strchr (item, '.');
+ if (period == NULL || *(period + 1) == '\0') {
+ fprintf (stderr,
+ "Invalid configuration name: %s\n"
+ "(Should be of the form <section>.<item>)\n", item);
+ return 1;
+ }
+
+ *period = '\0';
+ *key = period + 1;
+
+ return 0;
+}
+
+/* These are more properly called Xapian fields, but the user facing
+ * docs call them prefixes, so make the error message match */
+static bool
+validate_field_name (const char *str)
+{
+ const char *key;
+
+ if (! g_utf8_validate (str, -1, NULL)) {
+ fprintf (stderr, "Invalid utf8: %s\n", str);
+ return false;
+ }
+
+ key = g_utf8_strrchr (str, -1, '.');
+ if (! key ) {
+ INTERNAL_ERROR ("Impossible code path on input: %s\n", str);
+ }
+
+ key++;
+
+ if (! *key) {
+ fprintf (stderr, "Empty prefix name: %s\n", str);
+ return false;
+ }
+
+ if (! unicode_word_utf8 (key)) {
+ fprintf (stderr, "Non-word character in prefix name: %s\n", key);
+ return false;
+ }
+
+ if (key[0] >= 'a' && key[0] <= 'z') {
+ fprintf (stderr, "Prefix names starting with lower case letters are reserved: %s\n", key);
+ return false;
+ }
+
+ return true;
+}
+
+#define BUILT_WITH_PREFIX "built_with."
+
+typedef struct config_key {
+ const char *name;
+ bool in_db;
+ bool prefix;
+ bool (*validate)(const char *);
+} config_key_info_t;
+
+static struct config_key
+ config_key_table[] = {
+ { "index.decrypt", true, false, NULL },
+ { "index.header.", true, true, validate_field_name },
+ { "query.", true, true, NULL },
+};
+
+static config_key_info_t *
+_config_key_info (const char *item)
+{
+ for (size_t i = 0; i < ARRAY_SIZE (config_key_table); i++) {
+ if (config_key_table[i].prefix &&
+ strncmp (item, config_key_table[i].name,
+ strlen (config_key_table[i].name)) == 0)
+ return config_key_table + i;
+ if (strcmp (item, config_key_table[i].name) == 0)
+ return config_key_table + i;
+ }
+ return NULL;
+}