Merge branch 'ical'
[alpine.git] / pith / context.c
bloba0093b672daa978692c55163d7bef8d315cfbe51
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: context.c 1144 2008-08-14 16:53:34Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2007 University of Washington
8 * Copyright 2013-2017 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/conf.h"
21 #include "../pith/context.h"
22 #include "../pith/state.h"
23 #include "../pith/status.h"
24 #include "../pith/stream.h"
25 #include "../pith/folder.h"
26 #include "../pith/util.h"
27 #include "../pith/tempfile.h"
31 * Internal prototypes
33 char *context_percent_quote(char *);
36 /* Context Manager context format digester
37 * Accepts: context string and buffers for sprintf-suitable context,
38 * remote host (for display), remote context (for display),
39 * and view string
40 * Returns: NULL if successful, else error string
42 * Comments: OK, here's what we expect a fully qualified context to
43 * look like:
45 * [*] [{host ["/"<proto>] [:port]}] [<cntxt>] "[" [<view>] "]" [<cntxt>]
47 * 2) It's understood that double "[" or "]" are used to
48 * quote a literal '[' or ']' in a context name.
50 * 3) an empty view in context implies a view of '*', so that's
51 * what get's put in the view string
53 * The 2nd,3rd,4th, and 5th args all have length at least len.
56 char *
57 context_digest(char *context, char *scontext, char *host, char *rcontext,
58 char *view, size_t len)
60 char *p, *viewp = view;
61 char *scontextp = scontext;
62 int i = 0;
64 if((p = context) == NULL || *p == '\0'){
65 if(scontext) /* so the caller can apply */
66 strncpy(scontext, "%s", len); /* folder names as is. */
68 scontext[len-1] = '\0';
69 return(NULL); /* no error, just empty context */
72 /* find hostname if requested and exists */
73 if(*p == '{' || (*p == '*' && *++p == '{')){
74 for(p++; *p && *p != '}' ; p++)
75 if(host && p-context < len-1)
76 *host++ = *p; /* copy host if requested */
78 while(*p && *p != '}') /* find end of imap host */
79 p++;
81 if(*p == '\0')
82 return("Unbalanced '}'"); /* bogus. */
83 else
84 p++; /* move into next field */
87 for(; *p ; p++){ /* get thru context */
88 if(rcontext && i < len-1)
89 rcontext[i] = *p; /* copy if requested */
91 i++;
93 if(*p == '['){ /* done? */
94 if(*++p == '\0') /* look ahead */
95 return("Unbalanced '['");
97 if(*p != '[') /* not quoted: "[[" */
98 break;
100 else if(*p == ']' && *(p+1) == ']') /* these may be quoted, too */
101 p++;
104 if(*p == '\0')
105 return("No '[' in context");
107 for(; *p ; p++){ /* possibly in view portion ? */
108 if(*p == ']'){
109 if(*(p+1) == ']') /* is quoted */
110 p++;
111 else
112 break;
115 if(viewp && viewp-view < len-1)
116 *viewp++ = *p;
119 if(*p != ']')
120 return("No ']' in context");
122 for(; *p ; p++){ /* trailing context ? */
123 if(rcontext && i < len-1)
124 rcontext[i] = *p;
125 i++;
128 if(host) *host = '\0';
129 if(rcontext && i < len) rcontext[i] = '\0';
130 if(viewp) {
131 /* MAIL_LIST: dealt with it in new_context since could be either '%' or '*'
132 if(viewp == view && viewp-view < len-1)
133 *viewp++ = '*';
136 *viewp = '\0';
139 if(scontextp){ /* sprint'able context request ? */
140 if(*context == '*'){
141 if(scontextp-scontext < len-1)
142 *scontextp++ = *context;
144 context++;
147 if(*context == '{'){
148 while(*context && *context != '}'){
149 if(scontextp-scontext < len-1)
150 *scontextp++ = *context;
152 context++;
155 *scontextp++ = '}';
158 for(p = rcontext; *p ; p++){
159 if(*p == '[' && *(p+1) == ']'){
160 if(scontextp-scontext < len-2){
161 *scontextp++ = '%'; /* replace "[]" with "%s" */
162 *scontextp++ = 's';
165 p++; /* skip ']' */
167 else if(scontextp-scontext < len-1)
168 *scontextp++ = *p;
171 *scontextp = '\0';
174 return(NULL); /* no problems to report... */
178 /* Context Manager apply name to context
179 * Accepts: buffer to write, context to apply, ambiguous folder name
180 * Returns: buffer filled with fully qualified name in context
181 * No context applied if error
183 char *
184 context_apply(char *b, CONTEXT_S *c, char *name, size_t len)
186 if(!c || IS_REMOTE(name) ||
187 (!IS_REMOTE(c->context) && is_absolute_path(name))){
188 strncpy(b, name, len-1); /* no context! */
190 else if(name[0] == '#'){
191 if(IS_REMOTE(c->context)){
192 char *p = strchr(c->context, '}'); /* name specifies namespace */
193 snprintf(b, len, "%.*s", (int) MIN(p - c->context + 1, len-1), c->context);
194 b[MIN(p - c->context + 1, len-1)] = '\0';
195 snprintf(b+strlen(b), len-strlen(b), "%.*s", (int)(len-1-strlen(b)), name);
197 else{
198 strncpy(b, name, len-1);
201 else if(c->dir && c->dir->ref){ /* has reference string! */
202 snprintf(b, len, "%.*s", (int) len-1, c->dir->ref);
203 b[len-1] = '\0';
204 snprintf(b+strlen(b), len-strlen(b), "%.*s", (int) (len-1-strlen(b)), name);
206 else{ /* no ref, apply to context */
207 char *pq = NULL;
210 * Have to quote %s for the sprintf because we're using context
211 * as a format string.
213 pq = context_percent_quote(c->context);
215 if(strlen(c->context) + strlen(name) < len)
216 snprintf(b, len, pq, name);
217 else{
218 char *t;
219 size_t l;
221 l = strlen(pq)+strlen(name);
222 t = (char *) fs_get((l+1) * sizeof(char));
223 snprintf(t, l+1, pq, name);
224 strncpy(b, t, len-1);
225 fs_give((void **)&t);
228 if(pq)
229 fs_give((void **) &pq);
232 b[len-1] = '\0';
233 return(b);
238 * Insert % before existing %'s so printf will print a real %.
239 * This is a special routine just for contexts. It only does the % stuffing
240 * for %'s inside of the braces of the context name, not for %'s to
241 * the right of the braces, which we will be using for printf format strings.
242 * Returns a malloced string which the caller is responsible for.
244 char *
245 context_percent_quote(char *context)
247 char *pq = NULL;
249 if(!context || !*context)
250 pq = cpystr("");
251 else{
252 if(IS_REMOTE(context)){
253 char *end, *p, *q;
255 /* don't worry about size efficiency, just allocate double */
256 pq = (char *) fs_get((2*strlen(context) + 1) * sizeof(char));
258 end = strchr(context, '}');
259 p = context;
260 q = pq;
261 while(*p){
262 if(*p == '%' && p < end)
263 *q++ = '%';
265 *q++ = *p++;
268 *q = '\0';
270 else
271 pq = cpystr(context);
274 return(pq);
278 /* Context Manager check if name is ambiguous
279 * Accepts: candidate string
280 * Returns: T if ambiguous, NIL if fully-qualified
284 context_isambig (char *s)
286 return(!(*s == '{' || *s == '#'));
290 /*----------------------------------------------------------------------
291 Check to see if user is allowed to read or write this folder.
293 Args: s -- the name to check
295 Result: Returns 1 if OK
296 Returns 0 and posts an error message if access is denied
297 ----*/
299 context_allowed(char *s)
301 struct variable *vars = ps_global ? ps_global->vars : NULL;
302 int retval = 1;
303 MAILSTREAM stream; /* fake stream for error message in mm_notify */
305 if(ps_global
306 && ps_global->restricted
307 && (strindex("./~", s[0]) || srchstr(s, "/../"))){
308 stream.mailbox = s;
309 mm_notify(&stream, "Restricted mode doesn't allow operation", WARN);
310 retval = 0;
312 else if(vars && VAR_OPER_DIR
313 && s[0] != '{' && !(s[0] == '*' && s[1] == '{')
314 && strucmp(s,ps_global->inbox_name) != 0
315 && strcmp(s, ps_global->VAR_INBOX_PATH) != 0){
316 char *p, *free_this = NULL;
318 p = s;
319 if(strindex(s, '~')){
320 p = strindex(s, '~');
321 free_this = (char *)fs_get(strlen(p) + 200);
322 strncpy(free_this, p, strlen(p)+200);
323 fnexpand(free_this, strlen(p)+200);
324 p = free_this;
326 else if(p[0] != '/'){ /* add home dir to relative paths */
327 free_this = p = (char *)fs_get(strlen(s)
328 + strlen(ps_global->home_dir) + 2);
329 build_path(p, ps_global->home_dir, s,
330 strlen(s)+strlen(ps_global->home_dir)+2);
333 if(!in_dir(VAR_OPER_DIR, p)){
334 char err[200];
336 /* TRANSLATORS: User is restricted to operating within a certain directory */
337 snprintf(err, sizeof(err), _("Not allowed outside of %.150s"), VAR_OPER_DIR);
338 stream.mailbox = p;
339 mm_notify(&stream, err, WARN);
340 retval = 0;
342 else if(srchstr(p, "/../")){ /* check for .. in path */
343 stream.mailbox = p;
344 mm_notify(&stream, "\"..\" not allowed in name", WARN);
345 retval = 0;
348 if(free_this)
349 fs_give((void **)&free_this);
352 return retval;
357 /* Context Manager create mailbox
358 * Accepts: context
359 * mail stream
360 * mailbox name to create
361 * Returns: T on success, NIL on failure
364 long
365 context_create (CONTEXT_S *context, MAILSTREAM *stream, char *mailbox)
367 char tmp[MAILTMPLEN]; /* must be within context */
369 return(context_allowed(context_apply(tmp, context, mailbox, sizeof(tmp)))
370 ? pine_mail_create(stream,tmp) : 0L);
374 /* Context Manager open
375 * Accepts: context
376 * candidate stream for recycling
377 * mailbox name
378 * open options
379 * Returns: stream to use on success, NIL on failure
382 MAILSTREAM *
383 context_open (CONTEXT_S *context, MAILSTREAM *old, char *name, long int opt, long int *retflags)
385 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
387 if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp)))){
389 * If a stream is passed to context_open, we will either re-use
390 * it or close it.
392 if(old)
393 pine_mail_close(old);
395 return((MAILSTREAM *)NULL);
398 return(pine_mail_open(old, tmp, opt, retflags));
402 /* Context Manager status
403 * Accepts: context
404 * candidate stream for recycling
405 * mailbox name
406 * open options
407 * Returns: T if call succeeds, NIL on failure
410 long
411 context_status(CONTEXT_S *context, MAILSTREAM *stream, char *name, long int opt)
413 return(context_status_full(context, stream, name, opt, NULL, NULL));
417 long
418 context_status_full(CONTEXT_S *context, MAILSTREAM *stream, char *name,
419 long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext)
421 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
422 long flags = opt;
424 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
425 ? pine_mail_status_full(stream,tmp,flags,uidvalidity,uidnext) : 0L);
429 /* Context Manager status
431 * This is very similar to context_status. Instead of a stream pointer we
432 * receive a pointer to a pointer so that we can return a stream that we
433 * opened for further use by the caller.
435 * Accepts: context
436 * candidate stream for recycling
437 * mailbox name
438 * open options
439 * Returns: T if call succeeds, NIL on failure
442 long
443 context_status_streamp(CONTEXT_S *context, MAILSTREAM **streamp, char *name, long int opt)
445 return(context_status_streamp_full(context,streamp,name,opt,NULL,NULL));
449 long
450 context_status_streamp_full(CONTEXT_S *context, MAILSTREAM **streamp, char *name,
451 long int opt, imapuid_t *uidvalidity, imapuid_t *uidnext)
453 MAILSTREAM *stream;
454 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
456 if(!context_allowed(context_apply(tmp, context, name, sizeof(tmp))))
457 return(0L);
459 if(!streamp)
460 stream = NULL;
461 else{
462 if(!*streamp && IS_REMOTE(tmp)){
463 *streamp = pine_mail_open(NULL, tmp,
464 OP_SILENT|OP_HALFOPEN|SP_USEPOOL, NULL);
467 stream = *streamp;
470 return(pine_mail_status_full(stream, tmp, opt, uidvalidity, uidnext));
474 /* Context Manager rename
475 * Accepts: context
476 * mail stream
477 * old mailbox name
478 * new mailbox name
479 * Returns: T on success, NIL on failure
482 long
483 context_rename (CONTEXT_S *context, MAILSTREAM *stream, char *old, char *new)
485 char tmp[MAILTMPLEN],tmp2[MAILTMPLEN];
487 return((context_allowed(context_apply(tmp, context, old, sizeof(tmp)))
488 && context_allowed(context_apply(tmp2, context, new, sizeof(tmp2))))
489 ? pine_mail_rename(stream,tmp,tmp2) : 0L);
493 MAILSTREAM *
494 context_already_open_stream(CONTEXT_S *context, char *name, int flags)
496 char tmp[MAILTMPLEN];
498 return(already_open_stream(context_apply(tmp, context, name, sizeof(tmp)),
499 flags));
503 /* Context Manager delete mailbox
504 * Accepts: context
505 * mail stream
506 * mailbox name to delete
507 * Returns: T on success, NIL on failure
510 long
511 context_delete (CONTEXT_S *context, MAILSTREAM *stream, char *name)
513 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
515 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
516 ? pine_mail_delete(stream, tmp) : 0L);
520 /* Context Manager append message string
521 * Accepts: context
522 * mail stream
523 * destination mailbox
524 * stringstruct of message to append
525 * Returns: T on success, NIL on failure
528 long
529 context_append (CONTEXT_S *context, MAILSTREAM *stream, char *name, STRING *msg)
531 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
533 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
534 ? pine_mail_append(stream, tmp, msg) : 0L);
538 /* Context Manager append message string with flags
539 * Accepts: context
540 * mail stream
541 * destination mailbox
542 * flags to assign message being appended
543 * date of message being appended
544 * stringstruct of message to append
545 * Returns: T on success, NIL on failure
548 long
549 context_append_full(CONTEXT_S *context, MAILSTREAM *stream, char *name,
550 char *flags, char *date, STRING *msg)
552 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
554 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
555 ? pine_mail_append_full(stream, tmp, flags, date, msg) : 0L);
559 /* Context Manager append multiple message
560 * Accepts: context
561 * mail stream
562 * destination mailbox
563 * append data callback
564 * arbitrary ata for callback use
565 * Returns: T on success, NIL on failure
568 long
569 context_append_multiple(CONTEXT_S *context, MAILSTREAM *stream, char *name,
570 append_t af, APPENDPACKAGE *data, MAILSTREAM *not_this_stream)
572 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
574 return(context_allowed(context_apply(tmp, context, name, sizeof(tmp)))
575 ? pine_mail_append_multiple(stream, tmp, af, data, not_this_stream) : 0L);
579 /* Mail copy message(s)
580 * Accepts: context
581 * mail stream
582 * sequence
583 * destination mailbox
586 long
587 context_copy (CONTEXT_S *context, MAILSTREAM *stream, char *sequence, char *name)
589 char *s, tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
591 tmp[0] = '\0';
593 if(context_apply(tmp, context, name, sizeof(tmp))[0] == '{'){
594 if((s = strindex(tmp, '}')) != NULL)
595 s++;
596 else
597 return(0L);
599 else
600 s = tmp;
602 tmp[sizeof(tmp)-1] = '\0';
604 if(!*s)
605 strncpy(s = tmp, "INBOX", sizeof(tmp)); /* presume "inbox" ala c-client */
607 tmp[sizeof(tmp)-1] = '\0';
609 return(context_allowed(s) ? pine_mail_copy(stream, sequence, s) : 0L);
614 * Context manager stream usefulness test
615 * Accepts: context
616 * mail name
617 * mailbox name
618 * mail stream to test against mailbox name
619 * Returns: stream if useful, else NIL
621 MAILSTREAM *
622 context_same_stream(CONTEXT_S *context, char *name, MAILSTREAM *stream)
624 extern MAILSTREAM *same_stream(char *name, MAILSTREAM *stream);
625 char tmp[MAILTMPLEN]; /* build FQN from ambiguous name */
627 return(same_stream(context_apply(tmp, context, name, sizeof(tmp)), stream));
632 * new_context - creates and fills in a new context structure, leaving
633 * blank the actual folder list (to be filled in later as
634 * needed). Also, parses the context string provided
635 * picking out any user defined label. Context lines are
636 * of the form:
638 * [ ["] <string> ["] <white-space>] <context>
641 CONTEXT_S *
642 new_context(char *cntxt_string, int *prime)
644 CONTEXT_S *c;
645 char host[MAXPATH], rcontext[MAXPATH],
646 view[MAXPATH], dcontext[MAXPATH],
647 *nickname = NULL, *c_string = NULL, *p;
650 * do any context string parsing (like splitting user-supplied
651 * label from actual context)...
653 get_pair(cntxt_string, &nickname, &c_string, 0, 0);
655 if(update_bboard_spec(c_string, tmp_20k_buf, SIZEOF_20KBUF)){
656 fs_give((void **) &c_string);
657 c_string = cpystr(tmp_20k_buf);
660 if(c_string && *c_string == '\"')
661 (void)removing_double_quotes(c_string);
663 view[0] = rcontext[0] = host[0] = dcontext[0] = '\0';
664 if((p = context_digest(c_string, dcontext, host, rcontext, view, MAXPATH)) != NULL){
665 q_status_message2(SM_ORDER | SM_DING, 3, 4,
666 "Bad context, %.200s : %.200s", p, c_string);
667 fs_give((void **) &c_string);
668 if(nickname)
669 fs_give((void **)&nickname);
671 return(NULL);
673 else
674 fs_give((void **) &c_string);
676 c = (CONTEXT_S *) fs_get(sizeof(CONTEXT_S)); /* get new context */
677 memset((void *) c, 0, sizeof(CONTEXT_S)); /* and initialize it */
678 if(*host)
679 c->server = cpystr(host); /* server + params */
681 c->context = cpystr(dcontext);
683 if(strstr(c->context, "#news."))
684 c->use |= CNTXT_NEWS;
686 c->dir = new_fdir(NULL, view, (c->use & CNTXT_NEWS) ? '*' : '%');
688 /* fix up nickname */
689 if(!(c->nickname = nickname)){ /* make one up! */
690 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s%.100s",
691 (c->use & CNTXT_NEWS)
692 ? "News"
693 : (c->dir->ref)
694 ? (c->dir->ref) :"Mail",
695 (c->server) ? " on " : "",
696 (c->server) ? c->server : "");
697 c->nickname = cpystr(tmp_20k_buf);
700 if(prime && !*prime){
701 *prime = 1;
702 c->use |= CNTXT_SAVEDFLT;
705 /* fix up label */
706 if(NEWS_TEST(c)){
707 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%sews groups%s%.100s",
708 (*host) ? "N" : "Local n", (*host) ? " on " : "",
709 (*host) ? host : "");
711 else{
712 p = srchstr(rcontext, "[]");
713 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%solders%s%.100s in %.*s%s",
714 (*host) ? "F" : "Local f", (*host) ? " on " : "",
715 (*host) ? host : "",
716 p ? (int) MIN(p - rcontext, 100) : 0,
717 rcontext, (p && (p - rcontext) > 0) ? "" : "home directory");
720 c->label = cpystr(tmp_20k_buf);
722 dprint((5, "Context: %s serv:%s ref: %s view: %s\n",
723 c->context ? c->context : "?",
724 (c->server) ? c->server : "\"\"",
725 (c->dir->ref) ? c->dir->ref : "\"\"",
726 (c->dir->view.user) ? c->dir->view.user : "\"\""));
728 return(c);
733 * Release resources associated with global context list
735 void
736 free_contexts(CONTEXT_S **ctxt)
738 if(ctxt && *ctxt){
739 free_contexts(&(*ctxt)->next);
740 free_context(ctxt);
746 * Release resources associated with the given context structure
748 void
749 free_context(CONTEXT_S **cntxt)
751 if(cntxt && *cntxt){
752 if(*cntxt == ps_global->context_current)
753 ps_global->context_current = NULL;
755 if(*cntxt == ps_global->context_last)
756 ps_global->context_last = NULL;
758 if(*cntxt == ps_global->last_save_context)
759 ps_global->last_save_context = NULL;
761 if((*cntxt)->context)
762 fs_give((void **) &(*cntxt)->context);
764 if((*cntxt)->server)
765 fs_give((void **) &(*cntxt)->server);
767 if((*cntxt)->nickname)
768 fs_give((void **)&(*cntxt)->nickname);
770 if((*cntxt)->label)
771 fs_give((void **) &(*cntxt)->label);
773 if((*cntxt)->comment)
774 fs_give((void **) &(*cntxt)->comment);
776 if((*cntxt)->selected.reference)
777 fs_give((void **) &(*cntxt)->selected.reference);
779 if((*cntxt)->selected.sub)
780 free_selected(&(*cntxt)->selected.sub);
782 free_strlist(&(*cntxt)->selected.folders);
784 free_fdir(&(*cntxt)->dir, 1);
786 fs_give((void **)cntxt);