From d62ee33aeee47f7388f2c8636c7414ef414306fa Mon Sep 17 00:00:00 2001 From: Duncaen Date: Sun, 27 Jan 2019 22:18:22 +0100 Subject: [PATCH 01/20] mdeliver.1: add mrefile to SEE ALSO --- man/mdeliver.1 | 1 + 1 file changed, 1 insertion(+) diff --git a/man/mdeliver.1 b/man/mdeliver.1 index 07bc476..36a890f 100644 --- a/man/mdeliver.1 +++ b/man/mdeliver.1 @@ -69,6 +69,7 @@ Override the flags of the new message file to be .Ex -std .Sh SEE ALSO .Xr mexport 1 , +.Xr mrefile 1 , .Xr maildir 5 , .Xr mbox 5 .Pp From 694b7965c04737327052265d8e6eff16b9dc8849 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 10:53:05 +0100 Subject: [PATCH 02/20] mpick: add pipes and file redirection --- man/mpick.1 | 3 ++ mpick.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 112 insertions(+), 5 deletions(-) diff --git a/man/mpick.1 b/man/mpick.1 index 89bf619..2ad942e 100644 --- a/man/mpick.1 +++ b/man/mpick.1 @@ -96,6 +96,9 @@ tests are given by the following EBNF: | && -- conjunction | ! -- negation | ( ) + | "|" -- pipe current mail to command + | ">>" -- append current mail to file + | ">" -- write current mail to file | | | diff --git a/mpick.c b/mpick.c index 1704044..0427519 100644 --- a/mpick.c +++ b/mpick.c @@ -61,6 +61,8 @@ enum op { EXPR_REGEXI, EXPR_PRUNE, EXPR_PRINT, + EXPR_REDIR_FILE, + EXPR_REDIR_PIPE, EXPR_TYPE, EXPR_ALLSET, EXPR_ANYSET, @@ -143,6 +145,14 @@ struct thread { struct mlist *cur; }; +struct file { + enum op op; + const char *name; + const char *mode; + FILE *fp; + struct file *next; +}; + static struct thread *thr; static char *argv0; @@ -160,6 +170,8 @@ static char *pos; static time_t now; static int prune; +static struct file *files, *fileq = NULL; + static void ws() { @@ -179,6 +191,12 @@ token(char *token) } } +static int +peek(char *token) +{ + return strncmp(pos, token, strlen(token)) == 0; +} + noreturn static void parse_error(char *msg, ...) { @@ -642,14 +660,43 @@ parse_timecmp() return 0; } +static struct expr * +parse_redir(struct expr *e) +{ + char *s; + const char *m; + + if (peek("||")) + return e; + + if (token("|")) { + if (!parse_string(&s)) + parse_error("expected command"); + struct expr *r = mkexpr(EXPR_REDIR_PIPE); + r->a.string = s; + r->b.string = "w"; + return chain(e, EXPR_AND, r); + } + else if (token(">>")) m = "a+"; + else if (token(">")) m = "w+"; + else return e; + + if (!parse_string(&s)) + parse_error("expected file name"); + struct expr *r = mkexpr(EXPR_REDIR_FILE); + r->a.string = s; + r->b.string = m; + return chain(e, EXPR_AND, r); +} + static struct expr * parse_and() { - struct expr *e1 = parse_timecmp(); + struct expr *e1 = parse_redir(parse_timecmp()); struct expr *r = e1; while (token("&&")) { - struct expr *e2 = parse_timecmp(); + struct expr *e2 = parse_redir(parse_timecmp()); r = chain(r, EXPR_AND, e2); } @@ -843,9 +890,48 @@ msg_addr(struct mailinfo *m, char *h, int t) } } +FILE * +redir(struct expr *e) +{ + struct file *file; + FILE *fp; + + for (file = files; file; file = file->next) { + if (e->op == file->op && + strcmp(e->a.string, file->name) == 0 && + strcmp(e->b.string, file->mode) == 0) + return file->fp; + } + + fflush(stdout); + fp = NULL; + switch (e->op) { + case EXPR_REDIR_FILE: fp = fopen(e->a.string, e->b.string); break; + case EXPR_REDIR_PIPE: fp = popen(e->a.string, e->b.string); break; + } + if (!fp) { + fprintf(stderr, "%s: %s: %s\n", argv0, e->a.string, strerror(errno)); + exit(3); + } + file = calloc(1, sizeof (struct file)); + if (!file) { + perror("calloc"); + exit(2); + } + file->op = e->op; + file->name = e->a.string; + file->mode = e->b.string; + file->fp = fp; + if (!files) files = file; + if (fileq) fileq->next = file; + fileq = file; + return fp; +} + int eval(struct expr *e, struct mailinfo *m) { + FILE *fp; switch (e->op) { case EXPR_OR: return eval(e->a.expr, m) || eval(e->b.expr, m); @@ -859,6 +945,13 @@ eval(struct expr *e, struct mailinfo *m) return 1; case EXPR_PRINT: return 1; + case EXPR_REDIR_FILE: + case EXPR_REDIR_PIPE: + fp = redir(e); + fputs(m->fpath, fp); + putc('\n', fp); + fflush(fp); + return 1; case EXPR_LT: case EXPR_LE: case EXPR_EQ: @@ -1040,10 +1133,10 @@ do_thr() if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) { int i; for (i = 0; i < ml->m->depth; i++) - putchar(' '); + putc(' ', stdout); fputs(ml->m->fpath, stdout); - putchar('\n'); + putc('\n', stdout); kept++; } @@ -1126,7 +1219,8 @@ oneline(char *file) goto out; fputs(file, stdout); - putchar('\n'); + putc('\n', stdout); + fflush(stdout); kept++; out: @@ -1174,5 +1268,15 @@ main(int argc, char *argv[]) if (vflag) fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept); + + for (; files; files = fileq) { + fileq = files->next; + if (files->op == EXPR_REDIR_PIPE) + pclose(files->fp); + else + fclose(files->fp); + free(files); + } + return 0; } From c1fee74966c8ec70871364f205fd39604e696bc8 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 10:58:27 +0100 Subject: [PATCH 03/20] mpick: add skip expression --- man/mpick.1 | 1 + mpick.c | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/man/mpick.1 b/man/mpick.1 index 2ad942e..64a63d1 100644 --- a/man/mpick.1 +++ b/man/mpick.1 @@ -105,6 +105,7 @@ tests are given by the following EBNF: | | prune -- do not match further messages in thread | print -- always true value + | skip -- always false value ::= child | draft | flagged | info | new | parent | passed | replied | seen | selected | trashed diff --git a/mpick.c b/mpick.c index 0427519..c4a4207 100644 --- a/mpick.c +++ b/mpick.c @@ -274,6 +274,11 @@ parse_inner() } else if (token("print")) { struct expr *e = mkexpr(EXPR_PRINT); return e; + } else if (token("skip")) { + struct expr *e = mkexpr(EXPR_PRINT); + struct expr *not = mkexpr(EXPR_NOT); + not->a.expr = e; + return not; } else if (token("!")) { struct expr *e = parse_cmp(); struct expr *not = mkexpr(EXPR_NOT); From 8e29dd22917b4dbdeff2da9f48e8f705651d278e Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 11:12:18 +0100 Subject: [PATCH 04/20] mpick: add ternary (conditional) operator --- man/mpick.1 | 3 ++- mpick.c | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/man/mpick.1 b/man/mpick.1 index 64a63d1..744fa24 100644 --- a/man/mpick.1 +++ b/man/mpick.1 @@ -92,7 +92,8 @@ Unread messages. .Nm tests are given by the following EBNF: .Bd -literal - ::= || -- disjunction + ::= ? : -- ternary operator + | || -- disjunction | && -- conjunction | ! -- negation | ( ) diff --git a/mpick.c b/mpick.c index c4a4207..f9da79d 100644 --- a/mpick.c +++ b/mpick.c @@ -46,6 +46,7 @@ enum op { EXPR_OR = 1, EXPR_AND, + EXPR_COND, EXPR_NOT, EXPR_LT, EXPR_LE, @@ -115,7 +116,7 @@ struct expr { int64_t num; regex_t *regex; enum var var; - } a, b; + } a, b, c; int extra; }; @@ -263,7 +264,7 @@ parse_op() } static struct expr *parse_cmp(); -static struct expr *parse_or(); +static struct expr *parse_cond(); static struct expr * parse_inner() @@ -285,7 +286,7 @@ parse_inner() not->a.expr = e; return not; } else if (token("(")) { - struct expr *e = parse_or(); + struct expr *e = parse_cond(); if (token(")")) return e; parse_error("missing ) at '%.15s'", pos); @@ -723,10 +724,33 @@ parse_or() } static struct expr * -parse_expr(char *s) +parse_cond() +{ + struct expr *e1 = parse_or(); + + if (token("?")) { + struct expr *e2 = parse_or(); + if (token(":")) { + struct expr *e3 = parse_cond(); + struct expr *r = mkexpr(EXPR_COND); + r->a.expr = e1; + r->b.expr = e2; + r->c.expr = e3; + + return r; + } else { + parse_error("expected : at '%.15s'", pos); + } + } + + return e1; +} + +static struct expr * +parse_expr() { pos = s; - struct expr *e = parse_or(); + struct expr *e = parse_cond(); if (*pos) parse_error("trailing garbage at '%.15s'", pos); return e; @@ -942,6 +966,10 @@ eval(struct expr *e, struct mailinfo *m) return eval(e->a.expr, m) || eval(e->b.expr, m); case EXPR_AND: return eval(e->a.expr, m) && eval(e->b.expr, m); + case EXPR_COND: + return eval(e->a.expr, m) + ? eval(e->b.expr, m) + : eval(e->c.expr, m); case EXPR_NOT: return !eval(e->a.expr, m); return 1; From 86f24ed094a81f9763b0ea048d365174a79a0d90 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 14:38:05 +0100 Subject: [PATCH 05/20] mpick: add freeexpr --- mpick.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/mpick.c b/mpick.c index f9da79d..e1f9478 100644 --- a/mpick.c +++ b/mpick.c @@ -220,6 +220,50 @@ mkexpr(enum op op) return e; } +static void +freeexpr(struct expr *e) +{ + if (!e) return; + switch (e->op) { + case EXPR_OR: + case EXPR_AND: + freeexpr(e->a.expr); + freeexpr(e->b.expr); + break; + case EXPR_COND: + freeexpr(e->a.expr); + freeexpr(e->b.expr); + freeexpr(e->c.expr); + break; + case EXPR_NOT: + freeexpr(e->a.expr); + break; + case EXPR_REDIR_FILE: + case EXPR_REDIR_PIPE: + free(e->a.string); + free(e->b.string); + break; + case EXPR_STREQ: + case EXPR_STREQI: + case EXPR_GLOB: + case EXPR_GLOBI: + case EXPR_REGEX: + case EXPR_REGEXI: + switch (e->a.prop) { + case PROP_PATH: + case PROP_FROM: + case PROP_TO: + break; + default: free(e->a.string); + } + if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI) + regfree(e->b.regex); + else + free(e->b.string); + } + free(e); +} + static struct expr * chain(struct expr *e1, enum op op, struct expr *e2) { @@ -1299,6 +1343,8 @@ main(int argc, char *argv[]) if (Tflag && thr) do_thr(); + freeexpr(expr); + if (vflag) fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept); From ce5bab3f5fb0c17b11de579ecf7c90541f265b0e Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 14:45:40 +0100 Subject: [PATCH 06/20] mpick: add xcalloc and xstrdup --- mpick.c | 66 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/mpick.c b/mpick.c index e1f9478..78509d4 100644 --- a/mpick.c +++ b/mpick.c @@ -173,6 +173,26 @@ static int prune; static struct file *files, *fileq = NULL; +static void * +xcalloc(size_t nmemb, size_t size) +{ + void *r; + if ((r = calloc(nmemb, size))) + return r; + perror("calloc"); + exit(2); +} + +static char * +xstrdup(const char *s) +{ + char *r; + if ((r = strdup(s))) + return r; + perror("strdup"); + exit(2); +} + static void ws() { @@ -460,28 +480,27 @@ parse_strcmp() char *disp, *addr; blaze822_addr(s, &disp, &addr); if (!disp && !addr) - parse_error("invalid address at '%.15s'", pos); - s = strdup((disp) ? disp : addr); + parse_error_at(NULL, "invalid address"); + free(s); + s = xstrdup((disp) ? disp : addr); e->extra = (disp) ? 0 : 1; } - if (op == EXPR_REGEX) { - e->b.regex = malloc(sizeof (regex_t)); - r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB); - } else if (op == EXPR_REGEXI) { + if (op == EXPR_REGEX || op == EXPR_REGEXI) { e->b.regex = malloc(sizeof (regex_t)); - r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE); + r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | + (op == EXPR_REGEXI ? REG_ICASE : 0)); + if (r != 0) { + char msg[256]; + regerror(r, e->b.regex, msg, sizeof msg); + parse_error("invalid regex '%s': %s", s, msg); + exit(2); + } + free(s); } else { e->b.string = s; } - if (r != 0) { - char msg[256]; - regerror(r, e->b.regex, msg, sizeof msg); - parse_error("invalid regex '%s': %s", s, msg); - exit(2); - } - if (negate) { struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; @@ -724,7 +743,7 @@ parse_redir(struct expr *e) parse_error("expected command"); struct expr *r = mkexpr(EXPR_REDIR_PIPE); r->a.string = s; - r->b.string = "w"; + r->b.string = xstrdup("w"); return chain(e, EXPR_AND, r); } else if (token(">>")) m = "a+"; @@ -735,7 +754,7 @@ parse_redir(struct expr *e) parse_error("expected file name"); struct expr *r = mkexpr(EXPR_REDIR_FILE); r->a.string = s; - r->b.string = m; + r->b.string = xstrdup(m); return chain(e, EXPR_AND, r); } @@ -883,7 +902,7 @@ parse_msglist(char *s) if (!disp && !addr) parse_error("invalid address at '%.15s'", pos); - d = strdup((disp) ? disp : addr); + d = xstrdup((disp) ? disp : addr); e1 = mkexpr(EXPR_REGEXI); e1->a.prop = PROP_FROM; @@ -986,11 +1005,7 @@ redir(struct expr *e) fprintf(stderr, "%s: %s: %s\n", argv0, e->a.string, strerror(errno)); exit(3); } - file = calloc(1, sizeof (struct file)); - if (!file) { - perror("calloc"); - exit(2); - } + file = xcalloc(1, sizeof (struct file)); file->op = e->op; file->name = e->a.string; file->mode = e->b.string; @@ -1247,12 +1262,7 @@ collect(char *file) do_thr(); /* new thread */ - thr = calloc(1, sizeof *thr); - if (!thr) { - fprintf(stderr, "calloc"); - exit(2); - } - + thr = xcalloc(1, sizeof *thr); thr->matched = 0; ml = thr->cur = thr->childs; thr->cur->m = m; From 067f81e363d245abb11d1670bed5cef0b0c6fcad Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 15:00:56 +0100 Subject: [PATCH 07/20] mpick: add support for multiline expressions and comments --- mpick.c | 69 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/mpick.c b/mpick.c index 78509d4..eb6afc9 100644 --- a/mpick.c +++ b/mpick.c @@ -154,6 +154,12 @@ struct file { struct file *next; }; +struct pos { + char *pos; + char *line; + size_t linenr; +}; + static struct thread *thr; static char *argv0; @@ -167,10 +173,14 @@ static long num; static struct expr *expr; static long cur_idx; static char *cur; -static char *pos; static time_t now; static int prune; +static char *pos; +static const char *fname; +static char *line = NULL; +static int linenr = 0; + static struct file *files, *fileq = NULL; static void * @@ -196,8 +206,21 @@ xstrdup(const char *s) static void ws() { - while (isspace((unsigned char)*pos)) - pos++; + for (; *pos;) { + while (isspace((unsigned char)*pos)) { + if (*pos == '\n') { + line = pos+1; + linenr++; + } + pos++; + } + if (*pos != '#') + break; + + pos += strcspn(pos, "\n\0"); + if (*pos != '\n') + break; + } } static int @@ -223,12 +246,34 @@ parse_error(char *msg, ...) { va_list ap; va_start(ap, msg); - fprintf(stderr, "%s: parse error: ", argv0); + fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1); vfprintf(stderr, msg, ap); fprintf(stderr, "\n"); exit(2); } +noreturn static void +parse_error_at(struct pos *savepos, char *msg, ...) +{ + char *e; + + if (savepos) { + pos = savepos->pos; + line = savepos->line; + linenr = savepos->linenr; + } + + va_list ap; + va_start(ap, msg); + fprintf(stderr, "%s: parse error: %s:%d:%ld: ", argv0, fname, linenr, pos-line+1); + vfprintf(stderr, msg, ap); + fprintf(stderr, " at '"); + for (e = pos+15; *pos && *pos != '\n' && pos <= e; pos++) + putc(*pos, stderr); + fprintf(stderr, "'\n"); + exit(2); +} + static struct expr * mkexpr(enum op op) { @@ -460,11 +505,11 @@ parse_strcmp() else if (token("!==") || token("!=")) negate = 1, op = EXPR_STREQ; else - parse_error("invalid string operator at '%.15s'", pos); + parse_error_at(NULL, "invalid string operator"); char *s; if (!parse_string(&s)) { - parse_error("invalid string at '%.15s'", pos); + parse_error_at(NULL, "invalid string"); return 0; } @@ -598,7 +643,7 @@ parse_cmp() return parse_flag(); if (!(op = parse_op())) - parse_error("invalid comparison at '%.15s'", pos); + parse_error_at(NULL, "invalid comparison"); int64_t n; if (parse_num(&n)) { @@ -716,7 +761,7 @@ parse_timecmp() op = parse_op(); if (!op) - parse_error("invalid comparison at '%.15s'", pos); + parse_error_at(NULL, "invalid comparison"); int64_t n; if (parse_num(&n) || parse_dur(&n)) { @@ -740,7 +785,7 @@ parse_redir(struct expr *e) if (token("|")) { if (!parse_string(&s)) - parse_error("expected command"); + parse_error_at(NULL, "expected command"); struct expr *r = mkexpr(EXPR_REDIR_PIPE); r->a.string = s; r->b.string = xstrdup("w"); @@ -751,7 +796,7 @@ parse_redir(struct expr *e) else return e; if (!parse_string(&s)) - parse_error("expected file name"); + parse_error_at(NULL, "expected file name"); struct expr *r = mkexpr(EXPR_REDIR_FILE); r->a.string = s; r->b.string = xstrdup(m); @@ -802,7 +847,7 @@ parse_cond() return r; } else { - parse_error("expected : at '%.15s'", pos); + parse_error_at(NULL, "expected :", pos); } } @@ -815,7 +860,7 @@ parse_expr() pos = s; struct expr *e = parse_cond(); if (*pos) - parse_error("trailing garbage at '%.15s'", pos); + parse_error_at(NULL, "trailing garbage"); return e; } From 35f473afc6138a7b5d0060471b61bac821a64012 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 15:10:24 +0100 Subject: [PATCH 08/20] mpick: refactor mailfile handling - fix some memory leaks - fix handling of unexisting files/mthread containers - in oneline mode use one mailinfo struct for each line --- mpick.c | 131 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/mpick.c b/mpick.c index eb6afc9..27ee831 100644 --- a/mpick.c +++ b/mpick.c @@ -121,6 +121,7 @@ struct expr { }; struct mailinfo { + char *file; char *fpath; struct stat *sb; struct message *msg; @@ -973,8 +974,13 @@ msg_date(struct mailinfo *m) if (m->date) return m->date; - if (!m->msg) - m->msg = blaze822(m->fpath); + // XXX: date comparisation should handle zero dates + if (!m->msg && m->fpath) { + if (!(m->msg = blaze822(m->fpath))) { + m->fpath = NULL; + return -1; + } + } char *b; if (m->msg && (b = blaze822_hdr(m->msg, "date"))) @@ -986,28 +992,35 @@ msg_date(struct mailinfo *m) char * msg_hdr(struct mailinfo *m, const char *h) { - if (!m->msg) - m->msg = blaze822(m->fpath); + static char hdrbuf[4096]; - char *b; - if (!m->msg || !(b = blaze822_chdr(m->msg, h))) - goto err; + if (!m->msg && m->fpath) { + if (!(m->msg = blaze822(m->fpath))) { + m->fpath = NULL; + *hdrbuf = 0; + return hdrbuf; + } + } - char buf[4096]; - blaze822_decode_rfc2047(buf, b, sizeof buf - 1, "UTF-8"); - if (!*buf) - goto err; + char *b; + if (!m->msg || !(b = blaze822_chdr(m->msg, h))) { + *hdrbuf = 0; + return hdrbuf; + } - return strdup(buf); -err: - return ""; + blaze822_decode_rfc2047(hdrbuf, b, sizeof hdrbuf - 1, "UTF-8"); + return hdrbuf; } char * msg_addr(struct mailinfo *m, char *h, int t) { - if (!m->msg) - m->msg = blaze822(m->fpath); + if (!m->msg && m->fpath) { + if (!(m->msg = blaze822(m->fpath))) { + m->fpath = NULL; + return ""; + } + } char *b; if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0) @@ -1085,7 +1098,7 @@ eval(struct expr *e, struct mailinfo *m) case EXPR_REDIR_FILE: case EXPR_REDIR_PIPE: fp = redir(e); - fputs(m->fpath, fp); + fputs(m->file, fp); putc('\n', fp); fflush(fp); return 1; @@ -1099,15 +1112,15 @@ eval(struct expr *e, struct mailinfo *m) case EXPR_ANYSET: { long v = 0, n; - if (!m->sb && ( + if (!m->sb && m->fpath && ( e->a.prop == PROP_ATIME || e->a.prop == PROP_CTIME || e->a.prop == PROP_MTIME || - e->a.prop == PROP_SIZE) && - (m->sb = calloc(1, sizeof *m->sb)) && - stat(m->fpath, m->sb) != 0) { - fprintf(stderr, "stat"); - exit(2); + e->a.prop == PROP_SIZE)) { + m->sb = xcalloc(1, sizeof *m->sb); + if (stat(m->fpath, m->sb) == -1) + m->fpath = NULL; + // XXX: stat based expressions should handle 0 } n = e->b.num; @@ -1154,9 +1167,9 @@ eval(struct expr *e, struct mailinfo *m) case EXPR_GLOBI: case EXPR_REGEX: case EXPR_REGEXI: { - const char *s = ""; + const char *s; switch (e->a.prop) { - case PROP_PATH: s = m->fpath; break; + case PROP_PATH: s = m->fpath ? m->fpath : ""; break; case PROP_FROM: s = msg_addr(m, "from", e->extra); break; case PROP_TO: s = msg_addr(m, "to", e->extra); break; default: s = msg_hdr(m, e->a.string); break; @@ -1175,7 +1188,7 @@ eval(struct expr *e, struct mailinfo *m) } struct mailinfo * -mailfile(char *file) +mailfile(struct mailinfo *m, char *file) { static int init; if (!init) { @@ -1186,45 +1199,36 @@ mailfile(char *file) cur = blaze822_seq_cur(); init = 1; } + char *fpath = file; - struct mailinfo *m; - m = calloc(1, sizeof *m); - if (!m) { - fprintf(stderr, "calloc"); - exit(2); - } - m->fpath = file; m->index = num++; - m->flags = 0; - m->replies = 0; - m->depth = 0; - m->sb = 0; - m->msg = 0; + m->file = file; - while (*m->fpath == ' ' || *m->fpath == '\t') { + while (*fpath == ' ' || *fpath == '\t') { m->depth++; - m->fpath++; + fpath++; } - char *e = m->fpath + strlen(m->fpath) - 1; - while (m->fpath < e && (*e == ' ' || *e == '\t')) + char *e = fpath + strlen(fpath) - 1; + while (fpath < e && (*e == ' ' || *e == '\t')) *e-- = 0; - if (m->fpath[0] == '<') { + if (fpath[0] == '<') { m->flags |= FLAG_SEEN | FLAG_INFO; + m->fpath = NULL; return m; } - if ((e = strrchr(m->fpath, '/') - 1) && (e - m->fpath) >= 2 && + if ((e = strrchr(fpath, '/') - 1) && (e - fpath) >= 2 && *e-- == 'w' && *e-- == 'e' && *e-- == 'n') m->flags |= FLAG_NEW; - if (cur && strcmp(cur, m->fpath) == 0) { + if (cur && strcmp(cur, fpath) == 0) { m->flags |= FLAG_CUR; cur_idx = m->index; } - char *f = strstr(m->fpath, ":2,"); + char *f = strstr(fpath, ":2,"); if (f) { if (strchr(f, 'P')) m->flags |= FLAG_PASSED; @@ -1240,6 +1244,7 @@ mailfile(char *file) m->flags |= FLAG_FLAGGED; } + m->fpath = fpath; return m; } @@ -1268,11 +1273,7 @@ do_thr() break; if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) { - int i; - for (i = 0; i < ml->m->depth; i++) - putc(' ', stdout); - - fputs(ml->m->fpath, stdout); + fputs(ml->m->file, stdout); putc('\n', stdout); kept++; @@ -1285,7 +1286,7 @@ do_thr() if (ml->m->sb) free(ml->m->sb); - free(ml->m->fpath); + free(ml->m->file); free(ml->m); } @@ -1298,11 +1299,14 @@ collect(char *file) { struct mailinfo *m; struct mlist *ml; + char *f; - if ((m = mailfile(file)) == 0) - return; + f = xstrdup(file); + m = xcalloc(1, sizeof *m); + m = mailfile(m, f); if (m->depth == 0) { + /* process previous thread */ if (thr) do_thr(); @@ -1337,17 +1341,15 @@ collect(char *file) for (ml = ml->parent; ml; ml = ml->parent) ml->m->replies++; - - m->fpath = strdup(m->fpath); } void oneline(char *file) { - struct mailinfo *m; - - m = mailfile(file); - if (expr && !eval(expr, m)) + struct mailinfo m = { 0 }; + m.index = num++; + (void) mailfile(&m, file); + if (expr && !eval(expr, &m)) goto out; fputs(file, stdout); @@ -1356,11 +1358,10 @@ oneline(char *file) kept++; out: - if (m->msg) - blaze822_free(m->msg); - if (m->sb) - free(m->sb); - free(m); + if (m.msg) + blaze822_free(m.msg); + if (m.sb) + free(m.sb); } int From 66958e44f0d5eafadc7bd909079597bc85a40762 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 15:23:27 +0100 Subject: [PATCH 09/20] mpick: minor cleanups/memleak fixes for parse_msglist --- mpick.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/mpick.c b/mpick.c index 27ee831..16028a7 100644 --- a/mpick.c +++ b/mpick.c @@ -872,12 +872,13 @@ parse_msglist(char *s) int r; struct expr *e1, *e2; char *d; + enum flags flag; switch (*s) { case '/': s++; e1 = mkexpr(EXPR_REGEXI); - e1->a.string = "subject"; + e1->a.string = xstrdup("subject"); e1->b.regex = malloc(sizeof (regex_t)); r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE); if (r != 0) { @@ -887,13 +888,10 @@ parse_msglist(char *s) } return e1; case ':': - if (strlen(s) <= 1) - parse_error("missing type at '%.15s'", s); - - enum flags flag; n = 0; switch (*++s) { + case '\0': parse_error("missing flag at '%s'", s-1); case 'P': flag = FLAG_PASSED; break; case 'F': flag = FLAG_FLAGGED; break; case 'D': flag = FLAG_DRAFT; break; @@ -905,7 +903,7 @@ parse_msglist(char *s) case 'o': n = 1; /* FALL THROUGH */ case 'n': flag = FLAG_NEW; break; case 'R': flag = FLAG_REPLIED; break; - default: parse_error("unknown type at '%.15s'", s); + default: parse_error("unknown flag at '%s'", s); } e1 = mkexpr(EXPR_ANYSET); @@ -946,9 +944,9 @@ parse_msglist(char *s) blaze822_addr(s, &disp, &addr); if (!disp && !addr) - parse_error("invalid address at '%.15s'", pos); + parse_error("invalid address '%s'", s); - d = xstrdup((disp) ? disp : addr); + d = (disp) ? disp : addr; e1 = mkexpr(EXPR_REGEXI); e1->a.prop = PROP_FROM; From 0ec4e3332e53e44c56ccccda539675afbb51fb67 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 15:30:19 +0100 Subject: [PATCH 10/20] mpick: allow reading expressions from files i.e. using #!/bin/mpick --- mpick.c | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/mpick.c b/mpick.c index 16028a7..1a6c810 100644 --- a/mpick.c +++ b/mpick.c @@ -23,6 +23,7 @@ #endif #include +#include #include #include @@ -858,8 +859,18 @@ parse_cond() static struct expr * parse_expr() { - pos = s; - struct expr *e = parse_cond(); + ws(); + return parse_cond(); +} + +static struct expr * +parse_buf(const char *f, char *s) +{ + struct expr *e; + fname = f; + line = pos = s; + linenr = 1; + e = parse_expr(); if (*pos) parse_error_at(NULL, "trailing garbage"); return e; @@ -1377,16 +1388,40 @@ main(int argc, char *argv[]) while ((c = getopt(argc, argv, "Tt:v")) != -1) switch (c) { case 'T': Tflag = need_thr = 1; break; - case 't': expr = chain(expr, EXPR_AND, parse_expr(optarg)); break; + case 't': expr = chain(expr, EXPR_AND, parse_buf("argv", optarg)); break; case 'v': vflag = 1; break; default: fprintf(stderr, "Usage: %s [-Tv] [-t test] [msglist ...]\n", argv0); exit(1); } - if (optind != argc) - for (c = optind; c < argc; c++) + if (optind != argc) { + for (c = optind; c < argc; c++) { + if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0) + break; expr = chain(expr, EXPR_AND, parse_msglist(argv[c])); + } + + struct stat st; + int fd; + size_t len; + for (; c < argc; c++) { + if ((fd = open(argv[c], O_RDONLY)) == -1) + exit(1); + if (fstat(fd, &st) == -1) + exit(1); + len = st.st_size; + char *s = mmap(0, len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); + if (s == MAP_FAILED) { + perror("mmap"); + exit(1); + } + s[len+1] = '\0'; + close(fd); + expr = chain(expr, EXPR_AND, parse_buf(argv[c], s)); + munmap(s, len+1); + } + } if (isatty(0)) i = blaze822_loop1(":", need_thr ? collect : oneline); From 9e8989c1e638a7fc2c8359dfa76fe61f4e885963 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 15:41:29 +0100 Subject: [PATCH 11/20] mpick: better error for missing ) over multiple lines --- mpick.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mpick.c b/mpick.c index 1a6c810..71e27ee 100644 --- a/mpick.c +++ b/mpick.c @@ -381,11 +381,9 @@ static struct expr * parse_inner() { if (token("prune")) { - struct expr *e = mkexpr(EXPR_PRUNE); - return e; + return mkexpr(EXPR_PRUNE); } else if (token("print")) { - struct expr *e = mkexpr(EXPR_PRINT); - return e; + return mkexpr(EXPR_PRINT); } else if (token("skip")) { struct expr *e = mkexpr(EXPR_PRINT); struct expr *not = mkexpr(EXPR_NOT); @@ -396,11 +394,14 @@ parse_inner() struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; return not; - } else if (token("(")) { + } + if (peek("(")) { + struct pos savepos = { pos, line, linenr }; + (void) token("("); struct expr *e = parse_cond(); if (token(")")) return e; - parse_error("missing ) at '%.15s'", pos); + parse_error_at(&savepos, "unterminated ("); return 0; } else { parse_error("unknown expression at '%.15s'", pos); From 9456402d69785ad6a2e4e5d08db0795f0c6e12dd Mon Sep 17 00:00:00 2001 From: Duncaen Date: Mon, 28 Jan 2019 16:22:29 +0100 Subject: [PATCH 12/20] mpick: add let expressions --- man/mpick.1 | 11 +++++ mpick.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 121 insertions(+), 4 deletions(-) diff --git a/man/mpick.1 b/man/mpick.1 index 744fa24..53d5120 100644 --- a/man/mpick.1 +++ b/man/mpick.1 @@ -107,6 +107,8 @@ tests are given by the following EBNF: | prune -- do not match further messages in thread | print -- always true value | skip -- always false value + | + | ::= child | draft | flagged | info | new | parent | passed | replied | seen | selected | trashed @@ -149,6 +151,15 @@ tests are given by the following EBNF: ::= " ([^"] | "")+ " -- use "" for a single " inside " | $[A-Za-z0-9_]+ -- environment variable + +-- let expressions evaluate the expression following the `in` keyword, +-- the bindings are lazily evaluated. + ::= { let = } in + +-- Inside the scope previously defined idents are replaced with expressions + ::= + + ::= [A-Za-z_][A-Za-z0-9_]+ .Ed .Sh EXIT STATUS .Ex -std diff --git a/mpick.c b/mpick.c index 71e27ee..3dc6660 100644 --- a/mpick.c +++ b/mpick.c @@ -162,6 +162,21 @@ struct pos { size_t linenr; }; +struct binding { + char *name; + size_t nlen; + size_t refcount; + struct expr *expr; + struct binding *next; +}; + +struct scope { + struct binding *bindings; + struct scope *prev; +}; + +struct scope *scopeq = NULL; + static struct thread *thr; static char *argv0; @@ -374,9 +389,102 @@ parse_op() return 0; } -static struct expr *parse_cmp(); +static int +parse_ident(char **sp, size_t *lp) +{ + char *p = pos; + if (!isalpha(*pos) && *pos != '_') + return 0; + p++; + while (*p && (isalnum(*p) || *p == '_')) + p++; + *sp = pos; + *lp = p-pos; + pos = p; + ws(); + return 1; +} + +static struct expr * +parse_binding() +{ + struct scope *sc; + struct binding *b; + char *s; + size_t l = 0; + + if (parse_ident(&s, &l)) { + for (sc = scopeq; sc; sc = sc->prev) { + for (b = sc->bindings; b; b = b->next) { + if (b->nlen == l && strncmp(b->name, s, l) == 0) { + b->refcount++; + return b->expr; + } + } + } + } + // back to the start of the ident if there was one + pos = pos-l; + parse_error_at(NULL, "unknown expression"); + return 0; +} + static struct expr *parse_cond(); +static struct expr * +parse_let() +{ + if (!token("let")) + return parse_binding(); + + struct scope *sc; + char *s; + size_t l; + + sc = xcalloc(1, sizeof (struct scope)); + sc->prev = scopeq; + scopeq = sc; + + struct binding *b, *bq; + struct expr *e; + bq = NULL; + for (;;) { + if (!parse_ident(&s, &l)) + parse_error_at(NULL, "missing ident"); + if (!token("=")) + parse_error_at(NULL, "missing ="); + e = parse_cond(); + b = xcalloc(1, sizeof (struct binding)); + b->name = s; + b->nlen = l; + b->expr = e; + if (!sc->bindings) sc->bindings = b; + if (bq) bq->next = b; + bq = b; + if (!token("let")) + break; + } + if (!token("in")) + parse_error_at(NULL, "missing `in`"); + + e = parse_cond(); + + struct binding *bs; + for (b = sc->bindings; b; b = bs) { + bs = b->next; + if (b->refcount == 0) + freeexpr(b->expr); + free(b); + } + + scopeq = sc->prev; + free(sc); + + return e; +} + +static struct expr *parse_cmp(); + static struct expr * parse_inner() { @@ -403,10 +511,8 @@ parse_inner() return e; parse_error_at(&savepos, "unterminated ("); return 0; - } else { - parse_error("unknown expression at '%.15s'", pos); - return 0; } + return parse_let(); } static int From 2095a16bceeb4aec7165b1608ba73d02d2975a6a Mon Sep 17 00:00:00 2001 From: Duncaen Date: Tue, 29 Jan 2019 14:07:55 +0100 Subject: [PATCH 13/20] mpick: add header decodeop for better address matching --- man/mpick.1 | 10 ++- mpick.c | 176 +++++++++++++++++++++++++++++-------------------- t/2000-mpick.t | 4 +- 3 files changed, 115 insertions(+), 75 deletions(-) diff --git a/man/mpick.1 b/man/mpick.1 index 53d5120..bb7d324 100644 --- a/man/mpick.1 +++ b/man/mpick.1 @@ -103,6 +103,7 @@ tests are given by the following EBNF: | | | + | | | prune -- do not match further messages in thread | print -- always true value @@ -139,8 +140,13 @@ tests are given by the following EBNF: | T )? -- *1024*1024*1024*1024 | cur -- index of cur message - ::= from | subject | to - | -- header name + ::= from | to | subject | + + ::= . addr -- match address parts + | . disp -- match address display parts + | -- empty matches raw headers + + ::= path ::= == | = | != -- string (in)equality | === | !=== -- case insensitive string (in)equality diff --git a/mpick.c b/mpick.c index 3dc6660..bff90b8 100644 --- a/mpick.c +++ b/mpick.c @@ -80,11 +80,12 @@ enum prop { PROP_REPLIES, PROP_SIZE, PROP_TOTAL, - PROP_FROM, - PROP_TO, PROP_INDEX, PROP_DATE, PROP_FLAG, + PROP_HDR, + PROP_HDR_ADDR, + PROP_HDR_DISP, }; enum flags { @@ -332,16 +333,17 @@ freeexpr(struct expr *e) case EXPR_REGEX: case EXPR_REGEXI: switch (e->a.prop) { - case PROP_PATH: - case PROP_FROM: - case PROP_TO: - break; - default: free(e->a.string); + case PROP_PATH: break; + case PROP_HDR: + case PROP_HDR_ADDR: + case PROP_HDR_DISP: + free(e->b.string); + default: return; } if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI) - regfree(e->b.regex); + regfree(e->c.regex); else - free(e->b.string); + free(e->c.string); } free(e); } @@ -579,14 +581,25 @@ parse_strcmp() negate = 0; if (token("from")) - prop = PROP_FROM; + h = xstrdup("from"); else if (token("to")) - prop = PROP_TO; + h = xstrdup("to"); else if (token("subject")) - h = "subject"; + h = xstrdup("subject"); + else if (token("path")) + prop = PROP_PATH; else if (!parse_string(&h)) return parse_inner(); + if (!prop) { + if (token(".")) { + if (token("addr")) prop = PROP_HDR_ADDR; + else if (token("disp")) prop = PROP_HDR_DISP; + else parse_error_at(NULL, "unknown decode parameter"); + } else + prop = PROP_HDR; + } + if (token("~~~")) op = EXPR_GLOBI; else if (token("~~")) @@ -624,35 +637,29 @@ parse_strcmp() int r = 0; struct expr *e = mkexpr(op); - - if (prop) - e->a.prop = prop; - else - e->a.string = h; - - if (prop == PROP_FROM || prop == PROP_TO) { - char *disp, *addr; - blaze822_addr(s, &disp, &addr); - if (!disp && !addr) - parse_error_at(NULL, "invalid address"); - free(s); - s = xstrdup((disp) ? disp : addr); - e->extra = (disp) ? 0 : 1; + e->a.prop = prop; + switch (prop) { + case PROP_HDR: + case PROP_HDR_ADDR: + case PROP_HDR_DISP: + e->b.string = h; + break; + case PROP_PATH: break; } if (op == EXPR_REGEX || op == EXPR_REGEXI) { - e->b.regex = malloc(sizeof (regex_t)); - r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | + e->c.regex = malloc(sizeof (regex_t)); + r = regcomp(e->c.regex, s, REG_EXTENDED | REG_NOSUB | (op == EXPR_REGEXI ? REG_ICASE : 0)); if (r != 0) { char msg[256]; - regerror(r, e->b.regex, msg, sizeof msg); + regerror(r, e->c.regex, msg, sizeof msg); parse_error("invalid regex '%s': %s", s, msg); exit(2); } free(s); } else { - e->b.string = s; + e->c.string = s; } if (negate) { @@ -996,12 +1003,13 @@ parse_msglist(char *s) case '/': s++; e1 = mkexpr(EXPR_REGEXI); - e1->a.string = xstrdup("subject"); - e1->b.regex = malloc(sizeof (regex_t)); - r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE); + e1->a.prop = PROP_HDR; + e1->b.string = xstrdup("subject"); + e1->c.regex = malloc(sizeof (regex_t)); + r = regcomp(e1->c.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE); if (r != 0) { char msg[256]; - regerror(r, e1->b.regex, msg, sizeof msg); + regerror(r, e1->c.regex, msg, sizeof msg); parse_error("invalid regex '%s': %s", s, msg); } return e1; @@ -1067,14 +1075,14 @@ parse_msglist(char *s) d = (disp) ? disp : addr; e1 = mkexpr(EXPR_REGEXI); - e1->a.prop = PROP_FROM; - e1->b.regex = malloc(sizeof (regex_t)); - e1->extra = (disp) ? 0 : 1; + e1->a.prop = (disp) ? PROP_HDR_DISP : PROP_HDR_ADDR; + e1->b.string = xstrdup("from"); + e1->c.regex = malloc(sizeof (regex_t)); - r = regcomp(e1->b.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE); + r = regcomp(e1->c.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE); if (r != 0) { char msg[256]; - regerror(r, e1->b.regex, msg, sizeof msg); + regerror(r, e1->c.regex, msg, sizeof msg); parse_error("invalid regex '%s': %s", d, msg); } @@ -1106,54 +1114,64 @@ msg_date(struct mailinfo *m) } char * -msg_hdr(struct mailinfo *m, const char *h) +msg_hdr(char **s, const char *h, struct mailinfo *m) { static char hdrbuf[4096]; if (!m->msg && m->fpath) { if (!(m->msg = blaze822(m->fpath))) { m->fpath = NULL; - *hdrbuf = 0; - return hdrbuf; + return NULL; } } + // XXX: only return one header for now + if (*s) + return NULL; + char *b; - if (!m->msg || !(b = blaze822_chdr(m->msg, h))) { - *hdrbuf = 0; - return hdrbuf; - } + if (!m->msg || !(b = blaze822_chdr(m->msg, h))) + return NULL; + *s = b; blaze822_decode_rfc2047(hdrbuf, b, sizeof hdrbuf - 1, "UTF-8"); return hdrbuf; } char * -msg_addr(struct mailinfo *m, char *h, int t) +msg_hdr_addr(char **s, const char *h, struct mailinfo *m, int rdisp) { if (!m->msg && m->fpath) { if (!(m->msg = blaze822(m->fpath))) { m->fpath = NULL; - return ""; + return NULL; } } - char *b; - if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0) - return ""; + char *b = *s; + if (!b) { + if (!m->msg || !(b = blaze822_chdr(m->msg, h))) + return NULL; + } char *disp, *addr; - blaze822_addr(b, &disp, &addr); + *s = blaze822_addr(b, &disp, &addr); - if (t) { - if (!addr) - return ""; - return addr; - } else { - if (!disp) - return ""; + if (rdisp) return disp; - } + return addr; +} + +char * +msg_hdr_address(char **s, const char *h, struct mailinfo *m) +{ + return msg_hdr_addr(s, h, m, 0); +} + +char * +msg_hdr_display(char **s, const char *h, struct mailinfo *m) +{ + return msg_hdr_addr(s, h, m, 1); } FILE * @@ -1283,21 +1301,34 @@ eval(struct expr *e, struct mailinfo *m) case EXPR_GLOBI: case EXPR_REGEX: case EXPR_REGEXI: { - const char *s; + const char *s = NULL; + char *p = NULL; + char *(*fn)(char **, const char *, struct mailinfo *) = 0; + int rv = 0; switch (e->a.prop) { + case PROP_HDR: fn = msg_hdr; break; + case PROP_HDR_ADDR: fn = msg_hdr_address; break; + case PROP_HDR_DISP: fn = msg_hdr_display; break; case PROP_PATH: s = m->fpath ? m->fpath : ""; break; - case PROP_FROM: s = msg_addr(m, "from", e->extra); break; - case PROP_TO: s = msg_addr(m, "to", e->extra); break; - default: s = msg_hdr(m, e->a.string); break; + default: return 0; } - switch (e->op) { - case EXPR_STREQ: return strcmp(e->b.string, s) == 0; - case EXPR_STREQI: return strcasecmp(e->b.string, s) == 0; - case EXPR_GLOB: return fnmatch(e->b.string, s, 0) == 0; - case EXPR_GLOBI: return fnmatch(e->b.string, s, FNM_CASEFOLD) == 0; - case EXPR_REGEX: - case EXPR_REGEXI: return regexec(e->b.regex, s, 0, 0, 0) == 0; + for (;;) { + if (fn && !(s = fn(&p, e->b.string, m))) + break; + switch (e->op) { + case EXPR_STREQ: rv = strcmp(e->c.string, s) == 0; break; + case EXPR_STREQI: rv = strcasecmp(e->c.string, s) == 0; break; + case EXPR_GLOB: rv = fnmatch(e->c.string, s, 0) == 0; break; + case EXPR_GLOBI: + rv = fnmatch(e->c.string, s, FNM_CASEFOLD) == 0; break; + case EXPR_REGEX: + case EXPR_REGEXI: + rv = regexec(e->c.regex, s, 0, 0, 0) == 0; + break; + } + if (!fn || rv) return rv; } + return 0; } } return 0; @@ -1504,8 +1535,9 @@ main(int argc, char *argv[]) if (optind != argc) { for (c = optind; c < argc; c++) { - if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0) + if (strchr(argv[c], '/') && access(argv[c], R_OK) == 0) { break; + } expr = chain(expr, EXPR_AND, parse_msglist(argv[c])); } diff --git a/t/2000-mpick.t b/t/2000-mpick.t index 8e32aec..9af444d 100755 --- a/t/2000-mpick.t +++ b/t/2000-mpick.t @@ -1,7 +1,7 @@ #!/bin/sh -e cd ${0%/*} . ./lib.sh -plan 13 +plan 15 rm -rf test.dir mkdir test.dir @@ -66,5 +66,7 @@ check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l' check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l' check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"pdf\"" | wc -l' check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l' +check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l' +check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l' ) From 8c2c1640eb1d64f351e63e49eee28e9d9f82a88c Mon Sep 17 00:00:00 2001 From: Duncaen Date: Fri, 8 Feb 2019 19:48:02 +0100 Subject: [PATCH 14/20] t/2000-mpick.t: add tests for all the new features --- t/2000-mpick.t | 51 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/t/2000-mpick.t b/t/2000-mpick.t index 9af444d..802ea83 100755 --- a/t/2000-mpick.t +++ b/t/2000-mpick.t @@ -1,7 +1,7 @@ #!/bin/sh -e cd ${0%/*} . ./lib.sh -plan 15 +plan 26 rm -rf test.dir mkdir test.dir @@ -61,6 +61,12 @@ Greetings #application/pdf ../../mshow ! +cat <shebang +#!$(command -v mpick) +from.addr == "peter@example.org" && from.disp == "Peter Example" +! +chmod +x shebang + check 'search subject' 'mlist inbox | mpick /wow | grep -q inbox/cur/9:2,' check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l' check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l' @@ -68,5 +74,48 @@ check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \" check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l' check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l' check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l' +check_test 'shebang' -eq 2 'mlist inbox | ./shebang | wc -l' +check_test 'ternary' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\" ? print : skip" | wc -l' + +check_same 'pipe command' 'mlist inbox | mpick -t "print |\"cat -n\" && skip"' 'mlist inbox | cat -n' +check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox' +check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox' +check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox' + +cat <expr +let foo = from.addr == "peter@example.org" +let bar = from.disp == "Peter Example" +# random comment +in + foo && bar # another comment +! +check_test 'let expression' -eq 2 'mlist inbox | mpick ./expr | wc -l' + +cat <expr +let foo = + let bar = from.disp == "Peter Example" + in + bar && from.addr == "peter@example.org" +in + foo +! +check_test 'let expression nested' -eq 2 'mlist inbox | mpick ./expr | wc -l' + +cat <expr +let foo = from.addr == "peter@example.org" +let bar = foo && subject =~ "wow" +in + bar +! +check_test 'let scoping' -eq 1 'mlist inbox | mpick ./expr | wc -l' + +cat <expr +let foo = from.addr == "peter@example.org" +let bar = from.disp == "Peter Example" +in + foo |"sed ""s/^/1:&/""" && bar |"sed ""s/^/2:&/""" && skip +! +check_test 'multi redir' -eq 4 'mlist inbox | mpick ./expr | wc -l' +check_test 'multi redir prefixes' -eq 2 'mlist inbox | mpick ./expr | cut -d: -f1 | sort -u | wc -l' ) From 4b413aa6a192a81bd080cad47c393d843ef0a33c Mon Sep 17 00:00:00 2001 From: Duncaen Date: Thu, 6 Jun 2019 21:48:24 +0200 Subject: [PATCH 15/20] mpick: always point to malloc memory in parse_string --- mpick.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mpick.c b/mpick.c index bff90b8..45c350d 100644 --- a/mpick.c +++ b/mpick.c @@ -544,7 +544,7 @@ parse_string(char **s) buf[len] = 0; pos++; ws(); - *s = buf ? buf : ""; + *s = buf ? buf : xstrdup(""); return 1; } else if (*pos == '$') { char t; @@ -559,7 +559,7 @@ parse_string(char **s) *pos = 0; *s = getenv(e); if (!*s) - *s = ""; + *s = xstrdup(""); *pos = t; ws(); return 1; From 1d662aee124da84e80aa257b4161191c712e0e72 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Thu, 6 Jun 2019 21:48:57 +0200 Subject: [PATCH 16/20] mpick: fix memory leak in parse_dur --- mpick.c | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/mpick.c b/mpick.c index 45c350d..1e3d0de 100644 --- a/mpick.c +++ b/mpick.c @@ -791,20 +791,20 @@ parse_dur(int64_t *n) parse_error("can't stat file '%s': %s", s, strerror(errno)); *n = st.st_mtime; - return 1; + goto ret; } struct tm tm = { 0 }; r = strptime(s, "%Y-%m-%d %H:%M:%S", &tm); if (r && !*r) { *n = mktime(&tm); - return 1; + goto ret; } r = strptime(s, "%Y-%m-%d", &tm); if (r && !*r) { tm.tm_hour = tm.tm_min = tm.tm_sec = 0; *n = mktime(&tm); - return 1; + goto ret; } r = strptime(s, "%H:%M:%S", &tm); if (r && !*r) { @@ -813,7 +813,7 @@ parse_dur(int64_t *n) tm.tm_mon = tmnow->tm_mon; tm.tm_mday = tmnow->tm_mday; *n = mktime(&tm); - return 1; + goto ret; } r = strptime(s, "%H:%M", &tm); if (r && !*r) { @@ -823,39 +823,40 @@ parse_dur(int64_t *n) tm.tm_mday = tmnow->tm_mday; tm.tm_sec = 0; *n = mktime(&tm); - return 1; + goto ret; } if (*s == '-') { - s++; - - errno = 0; int64_t d; - d = strtol(s, &r, 10); + errno = 0; + d = strtol(s+1, &r, 10); if (errno == 0 && r[0] == 'd' && !r[1]) { struct tm *tmnow = localtime(&now); tmnow->tm_mday -= d; tmnow->tm_hour = tmnow->tm_min = tmnow->tm_sec = 0; *n = mktime(tmnow); - return 1; + goto ret; } if (errno == 0 && r[0] == 'h' && !r[1]) { *n = now - (d*60*60); - return 1; + goto ret; } if (errno == 0 && r[0] == 'm' && !r[1]) { *n = now - (d*60); - return 1; + goto ret; } if (errno == 0 && r[0] == 's' && !r[1]) { *n = now - d; - return 1; + goto ret; } - parse_error("invalid relative time format '%s'", s-1); + parse_error("invalid relative time format '%s'", s); } parse_error("invalid time format '%s'", s); return 0; +ret: + free(s); + return 1; } static struct expr * From a6ea4cb92bee43fe9ac66ad476355cff958491a9 Mon Sep 17 00:00:00 2001 From: Duncaen Date: Fri, 7 Jun 2019 01:24:49 +0200 Subject: [PATCH 17/20] mpick: use slurp instead of mmaping files --- mpick.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/mpick.c b/mpick.c index 1e3d0de..adf6b09 100644 --- a/mpick.c +++ b/mpick.c @@ -1542,24 +1542,17 @@ main(int argc, char *argv[]) expr = chain(expr, EXPR_AND, parse_msglist(argv[c])); } - struct stat st; - int fd; - size_t len; for (; c < argc; c++) { - if ((fd = open(argv[c], O_RDONLY)) == -1) - exit(1); - if (fstat(fd, &st) == -1) - exit(1); - len = st.st_size; - char *s = mmap(0, len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); - if (s == MAP_FAILED) { - perror("mmap"); + char *s; + off_t len; + int r = slurp(argv[c], &s, &len); + if (r != 0) { + fprintf(stderr, "%s: error opening file '%s': %s\n", + argv0, argv[c], strerror(r)); exit(1); } - s[len+1] = '\0'; - close(fd); expr = chain(expr, EXPR_AND, parse_buf(argv[c], s)); - munmap(s, len+1); + free(s); } } From 475fe4447ba5fb03a26bcc9893e5a2b7e21e5ae8 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 5 Feb 2020 15:30:53 +0100 Subject: [PATCH 18/20] mpick: fix memory leaks --- mpick.c | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/mpick.c b/mpick.c index adf6b09..d72301c 100644 --- a/mpick.c +++ b/mpick.c @@ -68,6 +68,7 @@ enum op { EXPR_TYPE, EXPR_ALLSET, EXPR_ANYSET, + EXPR_BINDING, }; enum prop { @@ -118,6 +119,7 @@ struct expr { int64_t num; regex_t *regex; enum var var; + struct binding *binding; } a, b, c; int extra; }; @@ -321,6 +323,13 @@ freeexpr(struct expr *e) case EXPR_NOT: freeexpr(e->a.expr); break; + case EXPR_BINDING: + e->a.binding->refcount -= 1; + if (e->a.binding->refcount == 0) { + freeexpr(e->a.binding->expr); + free(e->a.binding); + } + break; case EXPR_REDIR_FILE: case EXPR_REDIR_PIPE: free(e->a.string); @@ -338,12 +347,12 @@ freeexpr(struct expr *e) case PROP_HDR_ADDR: case PROP_HDR_DISP: free(e->b.string); - default: return; } if (e->op == EXPR_REGEX || e->op == EXPR_REGEXI) regfree(e->c.regex); else free(e->c.string); + break; } free(e); } @@ -419,8 +428,10 @@ parse_binding() for (sc = scopeq; sc; sc = sc->prev) { for (b = sc->bindings; b; b = b->next) { if (b->nlen == l && strncmp(b->name, s, l) == 0) { + struct expr *e = mkexpr(EXPR_BINDING); + e->a.binding = b; b->refcount++; - return b->expr; + return e; } } } @@ -460,9 +471,12 @@ parse_let() b->name = s; b->nlen = l; b->expr = e; - if (!sc->bindings) sc->bindings = b; - if (bq) bq->next = b; - bq = b; + if (bq) { + bq->next = b; + bq = b; + } else { + bq = sc->bindings = b; + } if (!token("let")) break; } @@ -474,8 +488,9 @@ parse_let() struct binding *bs; for (b = sc->bindings; b; b = bs) { bs = b->next; - if (b->refcount == 0) - freeexpr(b->expr); + if (b->refcount != 0) + continue; + freeexpr(b->expr); free(b); } @@ -1224,7 +1239,8 @@ eval(struct expr *e, struct mailinfo *m) : eval(e->c.expr, m); case EXPR_NOT: return !eval(e->a.expr, m); - return 1; + case EXPR_BINDING: + return eval(e->a.binding->expr, m); case EXPR_PRUNE: prune = 1; return 1; From 89be0ace74d43a06d2e2200422ec33aedb73e53c Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 5 Feb 2020 15:47:29 +0100 Subject: [PATCH 19/20] t/2000-mpick.t: check for double free with bindings --- t/2000-mpick.t | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/t/2000-mpick.t b/t/2000-mpick.t index 802ea83..3cff87e 100755 --- a/t/2000-mpick.t +++ b/t/2000-mpick.t @@ -1,7 +1,7 @@ #!/bin/sh -e cd ${0%/*} . ./lib.sh -plan 26 +plan 27 rm -rf test.dir mkdir test.dir @@ -91,6 +91,15 @@ in ! check_test 'let expression' -eq 2 'mlist inbox | mpick ./expr | wc -l' +cat <expr +let foo = from.addr == "peter@example.org" +let bar = from.disp == "Peter Example" +# random comment +in + foo && foo +! +check_test 'let expression double free' -eq 2 'mlist inbox | mpick ./expr | wc -l' + cat <expr let foo = let bar = from.disp == "Peter Example" From 65016bed8bef29ae2bebed958a763e7257e57652 Mon Sep 17 00:00:00 2001 From: Duncan Overbruck Date: Wed, 5 Feb 2020 16:07:41 +0100 Subject: [PATCH 20/20] mpick: improve unknown binding error and add test case --- mpick.c | 6 +++--- t/2000-mpick.t | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/mpick.c b/mpick.c index d72301c..5fcb308 100644 --- a/mpick.c +++ b/mpick.c @@ -423,6 +423,7 @@ parse_binding() struct binding *b; char *s; size_t l = 0; + struct pos savepos = { pos, line, linenr }; if (parse_ident(&s, &l)) { for (sc = scopeq; sc; sc = sc->prev) { @@ -436,9 +437,8 @@ parse_binding() } } } - // back to the start of the ident if there was one - pos = pos-l; - parse_error_at(NULL, "unknown expression"); + + parse_error_at(&savepos, "unknown expression"); return 0; } diff --git a/t/2000-mpick.t b/t/2000-mpick.t index 3cff87e..0b1296c 100755 --- a/t/2000-mpick.t +++ b/t/2000-mpick.t @@ -1,7 +1,7 @@ #!/bin/sh -e cd ${0%/*} . ./lib.sh -plan 27 +plan 28 rm -rf test.dir mkdir test.dir @@ -82,6 +82,9 @@ check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox' check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox' +check_same 'unknown ident' 'mlist inbox | mpick -t "let x = x in x" 2>&1' \ + "echo \"mpick: parse error: argv:1:9: unknown expression at 'x in x'\"" + cat <expr let foo = from.addr == "peter@example.org" let bar = from.disp == "Peter Example"