rohrpost

A commandline mail client to change the world as we see it.
git clone git://r-36.net/rohrpost
Log | Files | Refs | README | LICENSE

pick.c (9149B)


      1 /*
      2  * Copy me if you can.
      3  * by 20h
      4  */
      5 
      6 #include <unistd.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <strings.h>
     10 #include <string.h>
     11 #include <ctype.h>
     12 
     13 #include "ind.h"
     14 #include "arg.h"
     15 #include "cfg.h"
     16 #include "llist.h"
     17 #include "imap.h"
     18 #include "mark.h"
     19 #include "pick.h"
     20 
     21 enum {
     22 	PICK_NONE = 'O',
     23 	PICK_STR = 'S',
     24 	PICK_DATE = 'D',
     25 	PICK_NUM = 'N',
     26 	PICK_EXPR = 'E',
     27 	PICK_KEY = 'K',
     28 	PICK_SEQ = 'Q',
     29 	PICK_HEADERPREP = 'H',
     30 	PICK_END = '\0'
     31 };
     32 static char *desc[] = {
     33 	[PICK_NONE] = "None",
     34 	[PICK_STR] = "String",
     35 	[PICK_DATE] = "Date",
     36 	[PICK_NUM] = "Number",
     37 	[PICK_EXPR] = "Expression",
     38 	[PICK_KEY] = "Keyword",
     39 	[PICK_SEQ] = "Sequence (also: marks)",
     40 	[PICK_HEADERPREP] = "Header prepend"
     41 };
     42 
     43 typedef struct expression_t expression_t;
     44 struct expression_t {
     45        char *expr;
     46        char *syntax;
     47 };
     48 
     49 static expression_t expressions[] = {
     50 	{"ANSWERED", ""},
     51 	{"BCC", "S"},
     52 	{"BEFORE", "D"},
     53 	{"BODY", "S"},
     54 	{"CC", "S"},
     55 	{"DELETED", ""},
     56 	{"DRAFT", ""},
     57 	{"FLAGGED", ""},
     58 	{"FROM", "HS"},
     59 	{"HEADER", "SS"},
     60 	{"KEYWORD", "K"},
     61 	{"LARGER", "N"},
     62 	{"NEW", ""},
     63 	{"NOT", "E"},
     64 	{"OLD", ""},
     65 	{"ON", "D"},
     66 	{"OR", "EE"},
     67 	{"RECENT", ""},
     68 	{"SEEN", ""},
     69 	{"SENTBEFORE", "D"},
     70 	{"SENTON", "D"},
     71 	{"SENTSINCE", "D"},
     72 	{"SEQ", "Q"},
     73 	{"SINCE", "D"},
     74 	{"SMALLER", "N"},
     75 	{"SUBJECT", "HS"},
     76 	{"TEXT", "S"},
     77 	{"TO", "HS"},
     78 	{"UID", "Q"},
     79 	{"UNANSWERED", ""},
     80 	{"UNDELETED", ""},
     81 	{"UNDRAFTED", ""},
     82 	{"UNFLAGGED", ""},
     83 	{"UNKEYWORD", ""},
     84 	{"UNSEEN", ""}
     85 };
     86 
     87 static char *sortcriteria[] = {
     88 	"ARRIVAL",
     89 	"CC",
     90 	"DATE",
     91 	"FROM",
     92 	"REVERSE",
     93 	"SIZE",
     94 	"SUBJECT",
     95 	"TO"
     96 };
     97 
     98 /*
     99  * The order of this array is the order, in which the algorithm
    100  * for threading is selected.
    101  */
    102 static char *threadalgorithms[] = {
    103 	"REFERENCES",
    104 	"REFS",
    105 	"ORDEREDSUBJECT"
    106 };
    107 
    108 char *
    109 pick_mksearchstring(char *cfgn, char *mailbox, char **argv[])
    110 {
    111 	int j;
    112 	expression_t *expr;
    113 	char *rstr, *estr, *nestr, *astr, *idss;
    114 	llist_t *ids;
    115 
    116 	rstr = NULL;
    117 	estr = NULL;
    118 
    119 	do {
    120 		if ((*argv)[0][0] != ':')
    121 			die("Missing ':' at %s\n", (*argv)[0]);
    122 
    123 		expr = NULL;
    124 		for (j = 0;  j < nelem(expressions); j++) {
    125 			if (!strcasecmp((*argv)[0]+1, expressions[j].expr)) {
    126 				expr = &expressions[j];
    127 				break;
    128 			}
    129 		}
    130 		if (expr == NULL)
    131 			die("Expression '%s' unknown.\n", (*argv)[0]);
    132 
    133 		switch (expr->syntax[0]) {
    134 		case PICK_HEADERPREP:
    135 			nestr = smprintf("HEADER %s", expr->expr);
    136 			break;
    137 		case PICK_SEQ:
    138 			nestr = smprintf("");
    139 			break;
    140 		default:
    141 			nestr = smprintf("%s", expr->expr);
    142 			break;
    143 		}
    144 		if (estr == NULL) {
    145 			estr = nestr;
    146 		} else {
    147 			estr = smprintf("%s %s", estr, nestr);
    148 		}
    149 
    150 		*argv = &(*argv)[1];
    151 		for (j = 0; expr->syntax[j] != PICK_END; j++) {
    152 			if (!(*argv)[0]) {
    153 				die("Expected argument of type %s at '%s'\n",
    154 						desc[(int)expr->syntax[j]],
    155 						(*argv)[-1]);
    156 			}
    157 
    158 			astr = estr;
    159 			switch (expr->syntax[j]) {
    160 			case PICK_HEADERPREP:
    161 				continue;
    162 			case PICK_STR:
    163 				estr = smprintf("%s \"%s\"", estr, (*argv)[0]);
    164 				*argv = &(*argv)[1];
    165 				break;
    166 			case PICK_SEQ:
    167 				ids = imap_str2ids(cfgn, mailbox, (*argv)[0]);
    168 				idss = imap_ids2str(ids);
    169 				estr = smprintf("%s %s", estr, idss);
    170 				free(idss);
    171 				llist_free(ids);
    172 				*argv = &(*argv)[1];
    173 				break;
    174 			case PICK_NUM:
    175 			case PICK_KEY:
    176 			case PICK_DATE:
    177 				estr = smprintf("%s %s", estr, (*argv)[0]);
    178 				*argv = &(*argv)[1];
    179 				break;
    180 			case PICK_EXPR:
    181 				rstr = pick_mksearchstring(cfgn, mailbox, argv);
    182 				estr = smprintf("%s %s", estr, rstr);
    183 				free(rstr);
    184 				free(astr);
    185 				continue;
    186 			default:
    187 				continue;
    188 			}
    189 			free(astr);
    190 		}
    191 	} while((*argv)[0] != NULL);
    192 
    193 	return estr;
    194 }
    195 
    196 llist_t *
    197 pick_sanitizesort(llist_t *sortl)
    198 {
    199 	llistelem_t *elem;
    200 	int i, found, isreverse;
    201 
    202 	isreverse = 0;
    203 	forllist(sortl, elem) {
    204 		for (i = 0; i < strlen(elem->key); i++) {
    205 			if (islower(elem->key[i]))
    206 				elem->key[i] = toupper(elem->key[i]) & 0xFF;
    207 		}
    208 
    209 		found = 0;
    210 		for (i = 0; i < nelem(sortcriteria); i++) {
    211 			if (!strcmp(elem->key, sortcriteria[i]))
    212 				found = 1;
    213 		}
    214 		if (!found)
    215 			die("Sort criterion '%s' not supported.\n", elem->key);
    216 
    217 		if (!strcasecmp(elem->key, "REVERSE")) {
    218 			if (isreverse) {
    219 				die("You want to 'REVERSE REVERSE', which"
    220 						" is not possible.\n");
    221 			}
    222 			isreverse = 1;
    223 		} else {
    224 			isreverse = 0;
    225 		}
    226 	}
    227 
    228 	if (isreverse) {
    229 		die("Your last search criterion is 'REVERSE', which is"
    230 				" not possible.\n");
    231 	}
    232 
    233 	return sortl;
    234 }
    235 
    236 char *
    237 pick_threadalgorithm(imap_t *imap)
    238 {
    239 	llistelem_t *cap;
    240 	int i;
    241 
    242 	forllist(imap->caps, cap) {
    243 		if (!strncmp(cap->key, "THREAD", 6)) {
    244 			for (i = 0; i < nelem(threadalgorithms); i++) {
    245 				if (!strcmp(cap->key+7, threadalgorithms[i]))
    246 					return threadalgorithms[i];
    247 			}
    248 		}
    249 	}
    250 
    251 	return NULL;
    252 }
    253 
    254 void
    255 pick_printsearchsyntax(void)
    256 {
    257 	int i, j;
    258 
    259 	printf("SYNTAX:\n"
    260 			"The search syntax is a small layer above the "
    261 			"specification in IMAP4v1 (RFC3501).\n"
    262 			"Every expression begins with the search keyword, "
    263 			"prepended by a colon.\n"
    264 			"\n"
    265 			"Example: :or :text \"Hello World\" :unseen\n"
    266 			"\n"
    267 			"ARGUMENTS:\n"
    268 			"String: UTF-8 string\n"
    269 			"Date: e.g. \"5-JUL-2010\" RFC 2822 Section 3.3\n"
    270 			"Number: decimal number\n"
    271 			"Expression: an expression\n"
    272 			"Keyword: String\n"
    273 			"Sequence: e.g. 1,2,3,5:7,300:320\n"
    274 			"\n"
    275 			"EXPRESSIONS:\n"
    276 			"Expression: arguments\n");
    277 	for (i = 0; i < nelem(expressions); i++) {
    278 		printf("%s:", expressions[i].expr);
    279 		for (j = 0; expressions[i].syntax[j] != PICK_END; j++) {
    280 			if (expressions[i].syntax[j] == PICK_HEADERPREP)
    281 				continue;
    282 			printf(" %s", desc[(int)expressions[i].syntax[j]]);
    283 		}
    284 		printf("\n");
    285 	}
    286 }
    287 
    288 void
    289 pick_printsortsyntax(void)
    290 {
    291 	int i;
    292 
    293 	printf("\nSORT:\n"
    294 			"The -o flag is taking as an argument a list of "
    295 			"criteria.\n"
    296 			"They can be specified as upper- or lowercase.\n"
    297 			"\n"
    298 			"CRITERIA:\n");
    299 	for (i = 0; i < nelem(sortcriteria); i++)
    300 		printf("%s\n", sortcriteria[i]);
    301 }
    302 
    303 void
    304 pick_printthreadsyntax(void)
    305 {
    306 	int i;
    307 
    308 	printf("\nTHREAD:\n"
    309 			"The -t flag will trigger the thread structure of "
    310 			"the given search results to be written linearly "
    311 			"into the results sequence.\n"
    312 			"Following thread algorithms are defined in various "
    313 			"RFCs. The list given is the order in which on an "
    314 			"algorithm is decided.\n\n");
    315 	for (i = 0; i < nelem(threadalgorithms); i++)
    316 		printf("%s\n", threadalgorithms[i]);
    317 }
    318 
    319 void
    320 pick_printsyntax(void)
    321 {
    322 	pick_printsearchsyntax();
    323 	pick_printsortsyntax();
    324 	pick_printthreadsyntax();
    325 }
    326 
    327 void
    328 pickusage(char *argv0)
    329 {
    330 	die("usage: %s [-sqdht] [-c cfg] [-m folder] [-o sort criteria]"
    331 			" [-e seq] [search syntax]\n"
    332 			"See -s for syntax help.\n", argv0);
    333 }
    334 
    335 int
    336 pickmain(int argc, char *argv[])
    337 {
    338 	config_t *cfg;
    339 	imap_t *imap;
    340 	int status;
    341 	char *user, *pass, *netspec, *addseq, *sstr, *pstr, *selected,
    342 	     *sorts, *talg, *cfgn, *argv0;
    343 	llist_t *results, *sortl;
    344 	mark_t *marks;
    345 
    346 	enum {
    347 		BEQUIET = 0x01,
    348 		DRYRUN = 0x02,
    349 		PRINTSYNTAX = 0x04,
    350 		DOTHREAD = 0x08,
    351 
    352 		NOARGS = 0x10
    353 	};
    354 
    355 	status = 0;
    356 	addseq = "p";
    357 	sorts = NULL;
    358 	selected = NULL;
    359 	cfgn = NULL;
    360 
    361 	ARGBEGIN(argv0) {
    362 	case 'c':
    363 		cfgn = EARGF(pickusage(argv0));
    364 		break;
    365 	case 'd':
    366 		status |= DRYRUN;
    367 		break;
    368 	case 'e':
    369 		addseq = EARGF(pickusage(argv0));
    370 		break;
    371 	case 'm':
    372 		selected = EARGF(pickusage(argv0));
    373 		break;
    374 	case 'o':
    375 		sorts = EARGF(pickusage(argv0));
    376 		break;
    377 	case 'q':
    378 		status |= BEQUIET;
    379 		break;
    380 	case 's':
    381 		status |= PRINTSYNTAX;
    382 		break;
    383 	case 't':
    384 		status |= DOTHREAD;
    385 		break;
    386 	default:
    387 		pickusage(argv0);
    388 	} ARGEND;
    389 
    390 	if (status & PRINTSYNTAX) {
    391 		pick_printsyntax();
    392 		return 0;
    393 	}
    394 
    395 	if (argc < 1)
    396 		pickusage(argv0);
    397 
    398 	cfg = config_init(cfgn);
    399 	user = config_checkgetstr(cfg, "imapuser");
    400 	pass = config_checkgetstr(cfg, "imappass");
    401 	netspec = config_checkgetstr(cfg, "imapnet");
    402 	if (selected == NULL) {
    403 		selected = config_checkgetstr(cfg, "selected");
    404 	} else {
    405 		selected = memdups(selected);
    406 	}
    407 	if (cfg->name != NULL) {
    408 		cfgn = memdups(cfg->name);
    409 	} else {
    410 		cfgn = memdups(cfgn);
    411 	}
    412 
    413 	marks = mark_init(cfg->name, selected);
    414 	if (marks == NULL)
    415 		die("Could not initialize marks for %s.\n", addseq);
    416 
    417 	imap = imap_new(netspec, user, pass);
    418 
    419 	if (imap_init(imap))
    420 		imap_die(imap, "imap_init");
    421 	if (imap_select(imap, selected))
    422 		imap_die(imap, "imap_select");
    423 	config_free(cfg);
    424 
    425 	sstr = pick_mksearchstring(cfgn, selected, &argv);
    426 	free(cfgn);
    427 	if (status & DOTHREAD) {
    428 		talg = pick_threadalgorithm(imap);
    429 		if (talg == NULL) {
    430 			die("Could not find a supported threading "
    431 					"algorithm.\n");
    432 		}
    433 
    434 		results = imap_thread(imap, talg, sstr);
    435 	} else {
    436 		if (sorts != NULL) {
    437 			sortl = llist_splitstr(sorts, " ,");
    438 			sortl = pick_sanitizesort(sortl);
    439 			sorts = llist_joinstr(sortl, " ");
    440 			llist_free(sortl);
    441 			results = imap_sort(imap, sorts, sstr);
    442 			free(sorts);
    443 		} else {
    444 			results = imap_search(imap, sstr);
    445 		}
    446 	}
    447 	free(sstr);
    448 
    449 	if (results == NULL) {
    450 		printf("No results found.\n");
    451 		mark_stop(marks);
    452 		imap_close(imap);
    453 		imap_free(imap);
    454 		return 1;
    455 	}
    456 
    457 	pstr = llist_joinstr(results, " ");
    458 	llist_free(results);
    459 	mark_set(marks, addseq, pstr);
    460 	if (!(status & BEQUIET))
    461 		printf("%s\n", pstr);
    462 	free(pstr);
    463 
    464 	mark_stop(marks);
    465 	imap_close(imap);
    466 	imap_free(imap);
    467 	return 0;
    468 }
    469