├── .gitignore ├── Makefile ├── PKGBUILD ├── README.md └── mail-query.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.out 2 | *.o 3 | mail-query 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OUT = mail-query 2 | 3 | SRC = $(OUT).c 4 | OBJ = $(SRC:.c=.o) 5 | 6 | PREFIX ?= /usr/local 7 | 8 | CFLAGS := -std=c99 -g -pedantic -Wall -Wextra $(CFLAGS) 9 | 10 | all: $(OUT) 11 | 12 | $(OUT): $(OBJ) 13 | $(CC) -o $@ $(OBJ) $(LDFLAGS) 14 | 15 | install: all 16 | install -D -m755 $(OUT) $(DESTDIR)$(PREFIX)/bin/$(OUT) 17 | 18 | clean: 19 | $(RM) $(OUT) $(OBJ) 20 | 21 | .PHONY: install clean 22 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Patrick Brisbin 2 | 3 | pkgname=mail-query 4 | pkgver=0.0.1 5 | pkgrel=1 6 | pkgdesc="Parse a maildir and output addresses for use in mutt queries" 7 | arch=('any') 8 | url="https://github.com/pbrisbin/mail-query" 9 | license=('MIT') 10 | source=(${pkgname}.c Makefile) 11 | 12 | build() { 13 | make 14 | } 15 | 16 | package() { 17 | make PREFIX=/usr DESTDIR="$pkgdir" install 18 | } 19 | md5sums=('8dbf1d2e45438419abe683175b6d8d87' 20 | '8ede780eddfd8be5e8cadb7403a17699') 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mail Query 2 | 3 | **Archived**: I don't use or maintain this project anymore. Forks welcome. 4 | 5 | Query the contents of a Maildir (e.g. `~/Mail/INBOX`), for `From:` addresses. 6 | Print these addresses in the format expected by mutt as a `query_command`, 7 | similar to `abook --mutt-query foo`. 8 | 9 | ## Installation 10 | 11 | ```console 12 | make 13 | sudo make install 14 | ``` 15 | 16 | A `PKGBUILD` is present for Arch users. 17 | 18 | ## Usage 19 | 20 | Assuming your Maildir is at `~/Mail/INBOX`, add to your `muttrc`: 21 | 22 | ```muttrc 23 | set query_command = "mail-query '%s' ~/Mail/INBOX" 24 | ``` 25 | 26 | To decode [7-bit ASCII encoded MIME-headers][rfc2047] (starting, for example, 27 | with `=?UTF-8?` or `=?ISO-8859-1?`), ensure that `perl` is executable and the 28 | [`Encode::MIME:Header`][perl-mime-header] module is installed, then replace the 29 | above line in your `muttrc` by: 30 | 31 | [rfc2047]: https://tools.ietf.org/html/rfc2047 32 | [perl-mime-header]: https://perldoc.perl.org/Encode/MIME/Header.html 33 | 34 | ```muttrc 35 | set query_command= "mail-query '%s' ~/Mail/INBOX | perl -CS -MEncode -ne 'print decode(\"MIME-Header\", $_)'" 36 | ``` 37 | 38 | ## Extensions 39 | 40 | The plug-in [vim-mailquery][] lets you complete e-mail addresses inside vim via 41 | mail-query. 42 | 43 | [vim-mailquery]: https://github.com/Konfekt/vim-mailquery 44 | -------------------------------------------------------------------------------- /mail-query.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define REGEX_OPTS REG_ICASE|REG_EXTENDED|REG_NOSUB|REG_NEWLINE 14 | #define EMAIL_VERIFY_REGEX "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$" 15 | 16 | typedef struct __address_t { 17 | char *name; 18 | char *email; 19 | } address_t; 20 | 21 | static regex_t regex; 22 | static regex_t emailverifier; 23 | static address_t **entries = NULL; 24 | static int entry_count = 0; 25 | 26 | static void free_entries() { 27 | int i; 28 | address_t **entry; 29 | 30 | for (i = 0, entry = entries; i < entry_count; i++, entry++) { 31 | free((*entry)->name); 32 | free((*entry)->email); 33 | free((*entry)); 34 | } 35 | 36 | free(entries); 37 | } 38 | 39 | static int address_cmp(const void *a1, const void *a2) { 40 | address_t *address1 = *(address_t**)a1; 41 | address_t *address2 = *(address_t**)a2; 42 | 43 | return strcmp(address1->email, address2->email); 44 | } 45 | 46 | static char *strtrim(char *str) { 47 | char *pch = str; 48 | 49 | if (!str || *str == '\0') { 50 | return str; 51 | } 52 | 53 | while (isspace((unsigned char)*pch)) { 54 | pch++; 55 | } 56 | 57 | if (pch != str) { 58 | memmove(str, pch, (strlen(pch) + 1)); 59 | } 60 | 61 | if (*str == '\0') { 62 | return str; 63 | } 64 | 65 | pch = (str + (strlen(str) - 1)); 66 | 67 | while (isspace((unsigned char)*pch)) { 68 | pch--; 69 | } 70 | 71 | *++pch = '\0'; 72 | 73 | return str; 74 | } 75 | 76 | static void add_address(address_t *address) { 77 | if (!(entries = realloc(entries, ++entry_count * sizeof *entries))) { 78 | fprintf(stderr, "realloc failed"); 79 | exit(EXIT_FAILURE); 80 | } 81 | 82 | entries[entry_count - 1] = address; 83 | } 84 | 85 | static address_t *parse_from(char *line) { 86 | char *name, *email, *ptr; 87 | address_t *address; 88 | 89 | ptr = strchr(line, '<'); 90 | if (!ptr) { 91 | return NULL; 92 | } 93 | 94 | *ptr = '\0'; 95 | name = line; 96 | email = ptr + 1; 97 | *(ptr + 1 + strcspn(email, ">")) = '\0'; 98 | 99 | strtrim(name); 100 | strtrim(email); 101 | 102 | if (strlen(name) == 0 || strlen(email) == 0) { 103 | return NULL; 104 | } 105 | 106 | if (regexec(&emailverifier, email, 0, 0, 0) == REG_NOMATCH) { 107 | return NULL; 108 | } 109 | 110 | if (regexec(®ex, name, 0, 0, 0) == REG_NOMATCH && 111 | regexec(®ex, email, 0, 0, 0) == REG_NOMATCH) { 112 | return NULL; 113 | } 114 | 115 | address = malloc(sizeof *address); 116 | address->name = strdup(name); 117 | address->email = strdup(email); 118 | 119 | return address; 120 | } 121 | 122 | static int parse_mailfile(FILE *fp) { 123 | char line[PATH_MAX]; 124 | address_t *entry; 125 | 126 | while (fgets(line, PATH_MAX, fp)) { 127 | if (strncmp(line, "From: ", 6) == 0) { 128 | break; 129 | } 130 | } 131 | 132 | if ((entry = parse_from(&line[6]))) 133 | add_address(entry); 134 | 135 | return 0; 136 | } 137 | 138 | static void print_entries() { 139 | int i; 140 | char *prev_email = NULL; 141 | address_t **entry; 142 | 143 | if (entry_count == 0) { 144 | return; 145 | } 146 | 147 | printf("\n"); 148 | for (i = 0, entry = entries; i < entry_count; i++, entry++) { 149 | if (prev_email && strcmp(prev_email, (*entry)->email) == 0) { 150 | continue; 151 | } 152 | 153 | printf("%s\t%s\n", (*entry)->email, (*entry)->name); 154 | prev_email = (*entry)->email; 155 | } 156 | } 157 | 158 | static int walk_maildir(const char *path) { 159 | DIR *dirp; 160 | FILE *fp; 161 | struct dirent *dentry; 162 | char *subdir; 163 | char filename[PATH_MAX]; 164 | 165 | if (!(dirp = opendir(path))) { 166 | fprintf(stderr, "opendir: %s: %s\n", strerror(errno), path); 167 | return 1; 168 | } 169 | 170 | while ((dentry = readdir(dirp))) { 171 | if (dentry->d_type == DT_DIR) { 172 | if (strcmp(dentry->d_name, ".") == 0 || strcmp(dentry->d_name, "..") == 0) { 173 | continue; 174 | } 175 | 176 | asprintf(&subdir, "%s/%s", path, dentry->d_name); 177 | walk_maildir(subdir); 178 | free(subdir); 179 | } else if (dentry->d_type == DT_REG) { 180 | snprintf(filename, PATH_MAX, "%s/%s", path, dentry->d_name); 181 | 182 | if (!(fp = fopen(filename, "r"))) { 183 | fprintf(stderr, "fopen: %s: %s: ", filename, strerror(errno)); 184 | continue; 185 | } 186 | 187 | parse_mailfile(fp); 188 | fclose(fp); 189 | } 190 | } 191 | 192 | closedir(dirp); 193 | 194 | return 0; 195 | } 196 | 197 | int main(int argc, char *argv[]) { 198 | int i, ret; 199 | char errbuf[PATH_MAX]; 200 | 201 | if (argc < 3) { 202 | printf("usage: %s ...\n", argv[0]); 203 | return EXIT_FAILURE; 204 | } 205 | 206 | if ((ret = regcomp(®ex, argv[1], REGEX_OPTS)) != 0) { 207 | regerror(ret, ®ex, errbuf, PATH_MAX); 208 | fprintf(stderr, "failed to compile regex: %s: %s\n", errbuf, argv[1]); 209 | return EXIT_FAILURE; 210 | } 211 | 212 | if ((ret = regcomp(&emailverifier, EMAIL_VERIFY_REGEX, REGEX_OPTS)) != 0) { 213 | regerror(ret, ®ex, errbuf, PATH_MAX); 214 | fprintf(stderr, "failed to compile regex: %s: %s\n", errbuf, EMAIL_VERIFY_REGEX); 215 | return EXIT_FAILURE; 216 | } 217 | 218 | for (i = 2; i < argc; i++) 219 | walk_maildir(argv[i]); 220 | 221 | regfree(®ex); 222 | regfree(&emailverifier); 223 | 224 | qsort(entries, entry_count, sizeof *entries, address_cmp); 225 | print_entries(); 226 | free_entries(); 227 | 228 | return EXIT_SUCCESS; 229 | } 230 | 231 | --------------------------------------------------------------------------------