Add support for tab-completion when selecting by rule
[alpine.git] / pith / context.c
bloba66a23046872d0860238bdffd0ee8647a8a0e782
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2007 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/conf.h"
17 #include "../pith/context.h"
18 #include "../pith/state.h"
19 #include "../pith/status.h"
20 #include "../pith/stream.h"
21 #include "../pith/folder.h"
22 #include "../pith/util.h"
23 #include "../pith/tempfile.h"
27 * Internal prototypes
29 char *context_percent_quote(char *);
32 /* Context Manager context format digester
33 * Accepts: context string and buffers for sprintf-suitable context,
34 * remote host (for display), remote context (for display),
35 * and view string
36 * Returns: NULL if successful, else error string
38 * Comments: OK, here's what we expect a fully qualified context to
39 * look like:
41 * [*] [{host ["/"<proto>] [:port]}] [<cntxt>] "[" [<view>] "]" [<cntxt>]
43 * 2) It's understood that double "[" or "]" are used to
44 * quote a literal '[' or ']' in a context name.
46 * 3) an empty view in context implies a view of '*', so that's
47 * what gets put in the view string
49 * The 2nd,3rd,4th, and 5th args all have length at least len.
52 char *
53 context_digest(char *context, char *scontext, char *host, char *rcontext,
54 char *view, size_t len)
56 char *p, *viewp = view;
57 char *scontextp = scontext;
58 int i = 0;
60 if((p = context) == NULL || *p == '\0'){
61 if(scontext) /* so the caller can apply */
62 strncpy(scontext, "%s", len); /* folder names as is. */
64 scontext[len-1] = '\0';
65 return(NULL); /* no error, just empty context */
68 /* find hostname if requested and exists */
69 if(*p == '{' || (*p == '*' && *++p == '{')){
70 for(p++; *p && *p != '}' ; p++)
71 if(host && p-context < len-1)
72 *host++ = *p; /* copy host if requested */
74 while(*p && *p != '}') /* find end of imap host */
75 p++;
77 if(*p == '\0')
78 return("Unbalanced '}'"); /* bogus. */
79 else
80 p++; /* move into next field */
83 for(; *p ; p++){ /* get thru context */
84 if(rcontext && i < len-1)
85 rcontext[i] = *p; /* copy if requested */
87 i++;
89 if(*p == '['){ /* done? */
90 if(*++p == '\0') /* look ahead */
91 return("Unbalanced '['");
93 if(*p != '[') /* not quoted: "[[" */
94 break;
96 else if(*p == ']' && *(p+1) == ']') /* these may be quoted, too */
97 p++;
100 if(*p == '\0')
101 return("No '[' in context");
103 for(; *p ; p++){ /* possibly in view portion ? */
104 if(*p == ']'){
105 if(*(p+1) == ']') /* is quoted */
106 p++;
107 else
108 break;
111 if(viewp && viewp-view < len-1)
112 *viewp++ = *p;
115 if(*p != ']')
116 return("No ']' in context");
118 for(; *p ; p++){ /* trailing context ? */
119 if(rcontext && i < len-1)
120 rcontext[i] = *p;
121 i++;
124 if(host) *host = '\0';
125 if(rcontext && i < len) rcontext[i] = '\0';
126 if(viewp) {
127 /* MAIL_LIST: dealt with it in new_context since could be either '%' or '*'
128 if(viewp == view && viewp-view < len-1)
129 *viewp++ = '*';
132 *viewp = '\0';
135 if(scontextp){ /* sprint'able context request ? */
136 if(*context == '*'){
137 if(scontextp-scontext < len-1)
138 *scontextp++ = *context;
140 context++;
143 if(*context == '{'){
144 while(*context && *context != '}'){
145 if(scontextp-scontext < len-1)
146 *scontextp++ = *context;
148 context++;
151 *scontextp++ = '}';
154 for(p = rcontext; *p ; p++){
155 if(*p == '[' && *(p+1) == ']'){
156 if(scontextp-scontext < len-2){
157 *scontextp++ = '%'; /* replace "[]" with "%s" */
158 *scontextp++ = 's';
161 p++; /* skip ']' */
163 else if(scontextp-scontext < len-1)
164 *scontextp++ = *p;
167 *scontextp = '\0';
170 return(NULL); /* no problems to report... */
174 /* Context Manager apply name to context
175 * Accepts: buffer to write, context to apply, ambiguous folder name
176 * Returns: buffer filled with fully qualified name in context
177 * No context applied if error
179 char *
180 context_apply(char *b, CONTEXT_S *c, char *name, size_t len)
182 if(!c || IS_REMOTE(name) ||
183 (!IS_REMOTE(c->context) && is_absolute_path(name))){
184 strncpy(b, name, len-1); /* no context! */
186 else if(name[0] == '#'){
187 if(IS_REMOTE(c->context)){
188 char *p = strchr(c->context, '}'); /* name specifies namespace */
189 snprintf(b, len, "%.*s", (int) MIN(p - c->context + 1, len-1), c->context);
190 b[MIN(p - c->context + 1, len-1)] = '\0';
191 snprintf(b+strlen(b), len-strlen(b), "%.*s", (int)(len-1-strlen(b)), name);
193 else{
194 strncpy(b, name, len-1);
197 else if(c->dir && c->dir->ref){ /* has reference string! */
198 snprintf(b, len, "%.*s", (int) len-1, c->dir->ref);
199 b[len-1] = '\0';
200 snprintf(b+strlen(b), len-strlen(b), "%.*s", (int) (len-1-strlen(b)), name);
202 else{ /* no ref, apply to context */
203 char *pq = NULL;
206 * Have to quote %s for the sprintf because we're using context
207 * as a format string.
209 pq = context_percent_quote(c->context);
211 if(strlen(c->context) + strlen(name) < len)
212 snprintf(b, len, pq, name);
213 else{
214 char *t;
215 size_t l;
217 l = strlen(pq)+strlen(name);
218 t = (char *) fs_get((l+1) * sizeof(char));
219 snprintf(t, l+1, pq, name);
220 strncpy(b, t, len-1);
221 fs_give((void **)&t);
224 if(pq)
225 fs_give((void **) &pq);
228 b[len-1] = '\0';
229 return(b);
234 * Insert % before existing %'s so printf will print a real %.
235 * This is a special routine just for contexts. It only does the % stuffing
236 * for %'s inside of the braces of the context name, not for %'s to
237 * the right of the braces, which we will be using for printf format strings.
238 * Returns a malloced string which the caller is responsible for.
240 char *
241 context_percent_quote(char *context)
243 char *pq = NULL;
245 if(!context || !*context)
246 pq = cpystr("");
247 else{
248 if(IS_REMOTE(context)){
249 char *end, *p, *q;
251 /* don't worry about size efficiency, just allocate double */
252 pq = (char *) fs_get((2*strlen(context) + 1) * sizeof(char));
254 end = strchr(context, '}');
255 p = context;
256 q = pq;
257 while(*p){
258 if(*p == '%' && p < end)
259 *q++ = '%';
261 *q++ = *p++;
264 *q = '\0';
266 else
267 pq = cpystr(context);
270 return(pq);
274 /* Context Manager check if name is ambiguous
275 * Accepts: candidate string
276 * Returns: T if ambiguous, NIL if fully-qualified
280 context_isambig (char *s)
282 return(!(*s == '{' || *s == '#'));
286 /*----------------------------------------------------------------------
287 Check to see if user is allowed to read or write this folder.
289 Args: s -- the name to check
291 Result: Returns 1 if OK
292 Returns 0 and posts an error message if access is denied
293 ----*/
295 context_allowed(char *s)
297 struct variable *vars = ps_global ? ps_global->vars : NULL;
298 int retval = 1;
299 MAILSTREAM stream; /* fake stream for error message in mm_notify */
301 if(ps_global
302 && ps_global->restricted
303 && (strindex("./~", s[0]) || srchstr(s, "/../"))){
304 stream.mailbox = s;
305 mm_notify(&stream, "Restricted mode doesn't allow operation", WARN);
306 retval = 0;
308 else if(vars && VAR_OPER_DIR
309 && s[0] != '{' && !(s[0] == '*' && s[1] == '{')
310 && strucmp(s,ps_global->inbox_name) != 0
311 && strcmp(s, ps_global->VAR_INBOX_PATH) != 0){
312 char *p, *free_this = NULL;
314 p = s;
315 if(strindex(s, '~')){
316 p = strindex(s, '~');
317 free_this = (char *)fs_get(strlen(p) + 200);
318 strncpy(free_this, p, strlen(p)+200);
319 fnexpand(free_this, strlen(p)+200);
320 p = free_this;
322 else if(p[0] != '/'){ /* add home dir to relative paths */
323 free_this = p = (char *)fs_get(strlen(s)
324 + strlen(ps_global->home_dir) + 2);
325 build_path(p, ps_global->home_dir, s,
326 strlen(s)+strlen(ps_global->home_dir)+2);
329 if(!in_dir(VAR_OPER_DIR, p)){
330 char err[200];
332 /* TRANSLATORS: User is restricted to operating within a certain directory */
333 snprintf(err, sizeof(err), _("Not allowed outside of %.150s"), VAR_OPER_DIR);
334 stream.mailbox = p;
335 mm_notify(&stream, err, WARN);
336 retval = 0;
338 else if(srchstr(p, "/../")){ /* check for .. in path */
339 stream.mailbox = p;
340 mm_notify(&stream, "\"..\" not allowed in name", WARN);
341 retval = 0;
344 if(free_this)
345 fs_give((void **)&free_this);
348 return retval;
353 /* Context Manager create mailbox
354 * Accepts: context
355 * mail stream
356 * mailbox name to create
357 * Returns: T on success, NIL on failure
360 long
361 context_create (CONTEXT_S *context, MAILSTREAM *stream, char *mailbox)
363 char tmp[MAILTMPLEN]; /* must be within context */
365 return(context_allowed(context_apply(tmp, context, mailbox, sizeof(tmp)))
366 ? pine_mail_create(stream,tmp) : 0L);
370 /* Context Manager open
371 * Accepts: context
372 * candidate stream for recycling
373 * mailbox name
374 * open options
375 * Returns: stream to use on success, NIL on failure
378 MAILSTREAM *
379 context_open (CONTEXT_S *context, MAILSTREAM *old, char *name, long int opt, long int *retflags)
381 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
383 if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp)))){
385 * If a stream is passed to context_open, we will either re-use
386 * it or close it.
388 if(old)
389 pine_mail_close(old);
391 return((MAILSTREAM *)NULL);
394 return(pine_mail_open(old, tmp, opt, retflags));
398 /* Context Manager status
399 * Accepts: context
400 * candidate stream for recycling
401 * mailbox name
402 * open options
403 * Returns: T if call succeeds, NIL on failure
406 long
407 context_status(CONTEXT_S *context, MAILSTREAM *stream, char *name, long int opt)
409 return(context_status_full(context, stream, name, opt, NULL, NULL));
413 long
414 context_status_full(CONTEXT_S *context, MAILSTREAM *stream, char *name,
415 long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext)
417 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
418 long flags = opt;
420 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
421 ? pine_mail_status_full(stream,tmp,flags,uidvalidity,uidnext) : 0L);
425 /* Context Manager status
427 * This is very similar to context_status. Instead of a stream pointer we
428 * receive a pointer to a pointer so that we can return a stream that we
429 * opened for further use by the caller.
431 * Accepts: context
432 * candidate stream for recycling
433 * mailbox name
434 * open options
435 * Returns: T if call succeeds, NIL on failure
438 long
439 context_status_streamp(CONTEXT_S *context, MAILSTREAM **streamp, char *name, long int opt)
441 return(context_status_streamp_full(context,streamp,name,opt,NULL,NULL));
445 long
446 context_status_streamp_full(CONTEXT_S *context, MAILSTREAM **streamp, char *name,
447 long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext)
449 MAILSTREAM *stream;
450 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
452 if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp))))
453 return(0L);
455 if(!streamp)
456 stream = NULL;
457 else{
458 if(!*streamp && IS_REMOTE(tmp)){
459 *streamp = pine_mail_open(NULL, tmp,
460 OP_SILENT|OP_HALFOPEN|SP_USEPOOL, NULL);
463 stream = *streamp;
466 return(pine_mail_status_full(stream, tmp, opt, uidvalidity, uidnext));
470 /* Context Manager rename
471 * Accepts: context
472 * mail stream
473 * old mailbox name
474 * new mailbox name
475 * Returns: T on success, NIL on failure
478 long
479 context_rename (CONTEXT_S *context, MAILSTREAM *stream, char *old, char *new)
481 char tmp[MAILTMPLEN],tmp2[MAILTMPLEN];
483 return((context_allowed(context_apply(tmp, context, old, sizeof(tmp)))
484 && context_allowed(context_apply(tmp2, context, new, sizeof(tmp2))))
485 ? pine_mail_rename(stream,tmp,tmp2) : 0L);
489 MAILSTREAM *
490 context_already_open_stream(CONTEXT_S *context, char *name, int flags)
492 char tmp[MAILTMPLEN];
494 return(already_open_stream(context_apply(tmp, context, name, sizeof(tmp)),
495 flags));
499 /* Context Manager delete mailbox
500 * Accepts: context
501 * mail stream
502 * mailbox name to delete
503 * Returns: T on success, NIL on failure
506 long
507 context_delete (CONTEXT_S *context, MAILSTREAM *stream, char *name)
509 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
511 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
512 ? pine_mail_delete(stream, tmp) : 0L);
516 /* Context Manager append message string
517 * Accepts: context
518 * mail stream
519 * destination mailbox
520 * stringstruct of message to append
521 * Returns: T on success, NIL on failure
524 long
525 context_append (CONTEXT_S *context, MAILSTREAM *stream, char *name, STRING *msg)
527 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
529 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
530 ? pine_mail_append(stream, tmp, msg) : 0L);
534 /* Context Manager append message string with flags
535 * Accepts: context
536 * mail stream
537 * destination mailbox
538 * flags to assign message being appended
539 * date of message being appended
540 * stringstruct of message to append
541 * Returns: T on success, NIL on failure
544 long
545 context_append_full(CONTEXT_S *context, MAILSTREAM *stream, char *name,
546 char *flags, char *date, STRING *msg)
548 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
550 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
551 ? pine_mail_append_full(stream, tmp, flags, date, msg) : 0L);
555 /* Context Manager append multiple message
556 * Accepts: context
557 * mail stream
558 * destination mailbox
559 * append data callback
560 * arbitrary ata for callback use
561 * Returns: T on success, NIL on failure
564 long
565 context_append_multiple(CONTEXT_S *context, MAILSTREAM *stream, char *name,
566 append_t af, APPENDPACKAGE *data, MAILSTREAM *not_this_stream)
568 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
570 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
571 ? pine_mail_append_multiple(stream, tmp, af, data, not_this_stream) : 0L);
575 /* Mail copy message(s)
576 * Accepts: context
577 * mail stream
578 * sequence
579 * destination mailbox
582 long
583 context_copy (CONTEXT_S *context, MAILSTREAM *stream, char *sequence, char *name)
585 char *s, tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
587 tmp[0] = '\0';
589 if(context_apply(tmp, context, name, sizeof(tmp))[0] == '{'){
590 if((s = strindex(tmp, '}')) != NULL)
591 s++;
592 else
593 return(0L);
595 else
596 s = tmp;
598 tmp[sizeof(tmp)-1] = '\0';
600 if(!*s)
601 strncpy(s = tmp, "INBOX", sizeof(tmp)); /* presume "inbox" ala c-client */
603 tmp[sizeof(tmp)-1] = '\0';
605 return(context_allowed(s) ? pine_mail_copy(stream, sequence, s) : 0L);
610 * Context manager stream usefulness test
611 * Accepts: context
612 * mail name
613 * mailbox name
614 * mail stream to test against mailbox name
615 * Returns: stream if useful, else NIL
617 MAILSTREAM *
618 context_same_stream(CONTEXT_S *context, char *name, MAILSTREAM *stream)
620 extern MAILSTREAM *same_stream(char *name, MAILSTREAM *stream);
621 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
623 return(same_stream(context_apply(tmp, context, name, sizeof(tmp)), stream));
628 * new_context - creates and fills in a new context structure, leaving
629 * blank the actual folder list (to be filled in later as
630 * needed). Also, parses the context string provided
631 * picking out any user defined label. Context lines are
632 * of the form:
634 * [ ["] <string> ["] <white-space>] <context>
637 CONTEXT_S *
638 new_context(char *cntxt_string, int *prime)
640 CONTEXT_S *c;
641 char host[MAXPATH], rcontext[MAXPATH],
642 view[MAXPATH], dcontext[MAXPATH],
643 *nickname = NULL, *c_string = NULL, *p;
646 * do any context string parsing (like splitting user-supplied
647 * label from actual context)...
649 get_pair(cntxt_string, &nickname, &c_string, 0, 0);
651 if(update_bboard_spec(c_string, tmp_20k_buf, SIZEOF_20KBUF)){
652 fs_give((void **) &c_string);
653 c_string = cpystr(tmp_20k_buf);
656 if(c_string && *c_string == '\"')
657 (void)removing_double_quotes(c_string);
659 view[0] = rcontext[0] = host[0] = dcontext[0] = '\0';
660 if((p = context_digest(c_string, dcontext, host, rcontext, view, MAXPATH)) != NULL){
661 q_status_message2(SM_ORDER | SM_DING, 3, 4,
662 "Bad context, %.200s : %.200s", p, c_string);
663 fs_give((void **) &c_string);
664 if(nickname)
665 fs_give((void **)&nickname);
667 return(NULL);
669 else
670 fs_give((void **) &c_string);
672 c = (CONTEXT_S *) fs_get(sizeof(CONTEXT_S)); /* get new context */
673 memset((void *) c, 0, sizeof(CONTEXT_S)); /* and initialize it */
674 if(*host)
675 c->server = cpystr(host); /* server + params */
677 c->context = cpystr(dcontext);
679 if(strstr(c->context, "#news."))
680 c->use |= CNTXT_NEWS;
682 c->dir = new_fdir(NULL, view, (c->use & CNTXT_NEWS) ? '*' : '%');
684 /* fix up nickname */
685 if(!(c->nickname = nickname)){ /* make one up! */
686 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%.100s",
687 (c->use & CNTXT_NEWS)
688 ? "News"
689 : (c->dir->ref)
690 ? (c->dir->ref) :"Mail",
691 (c->server) ? " on " : "",
692 (c->server) ? c->server : "");
693 c->nickname = cpystr(tmp_20k_buf);
696 if(prime && !*prime){
697 *prime = 1;
698 c->use |= CNTXT_SAVEDFLT;
701 /* fix up label */
702 if(NEWS_TEST(c)){
703 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%sews groups%s%.100s",
704 (*host) ? "N" : "Local n", (*host) ? " on " : "",
705 (*host) ? host : "");
707 else{
708 p = srchstr(rcontext, "[]");
709 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%solders%s%.100s in %.*s%s",
710 (*host) ? "F" : "Local f", (*host) ? " on " : "",
711 (*host) ? host : "",
712 p ? (int) MIN(p - rcontext, 100) : 0,
713 rcontext, (p && (p - rcontext) > 0) ? "" : "home directory");
716 c->label = cpystr(tmp_20k_buf);
718 dprint((5, "Context: %s serv:%s ref: %s view: %s\n",
719 c->context ? c->context : "?",
720 (c->server) ? c->server : "\"\"",
721 (c->dir->ref) ? c->dir->ref : "\"\"",
722 (c->dir->view.user) ? c->dir->view.user : "\"\""));
724 return(c);
729 * Release resources associated with global context list
731 void
732 free_contexts(CONTEXT_S **ctxt)
734 if(ctxt && *ctxt){
735 free_contexts(&(*ctxt)->next);
736 free_context(ctxt);
742 * Release resources associated with the given context structure
744 void
745 free_context(CONTEXT_S **cntxt)
747 if(cntxt && *cntxt){
748 if(*cntxt == ps_global->context_current)
749 ps_global->context_current = NULL;
751 if(*cntxt == ps_global->context_last)
752 ps_global->context_last = NULL;
754 if(*cntxt == ps_global->last_save_context)
755 ps_global->last_save_context = NULL;
757 if((*cntxt)->context)
758 fs_give((void **) &(*cntxt)->context);
760 if((*cntxt)->server)
761 fs_give((void **) &(*cntxt)->server);
763 if((*cntxt)->nickname)
764 fs_give((void **)&(*cntxt)->nickname);
766 if((*cntxt)->label)
767 fs_give((void **) &(*cntxt)->label);
769 if((*cntxt)->comment)
770 fs_give((void **) &(*cntxt)->comment);
772 if((*cntxt)->selected.reference)
773 fs_give((void **) &(*cntxt)->selected.reference);
775 if((*cntxt)->selected.sub)
776 free_selected(&(*cntxt)->selected.sub);
778 free_strlist(&(*cntxt)->selected.folders);
780 free_fdir(&(*cntxt)->dir, 1);
782 fs_give((void **)cntxt);