#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: context.c,v 4.58 1998/08/29 06:33:33 mikes Exp $";
#endif
/*
 * Program:	Mailbox Context Management
 *
 * Author:	Mark Crispin
 *		Networks and Distributed Computing
 *		Computing & Communications
 *		University of Washington
 *		Administration Building, AG-44
 *		Seattle, WA  98195
 *		Internet: MRC@CAC.Washington.EDU
 *
 *
 * Pine and Pico are registered trademarks of the University of Washington.
 * No commercial use of these trademarks may be made without prior written
 * permission of the University of Washington.
 *
 * Pine, Pico, and Pilot software and its included text are Copyright
 * 1989-1998 by the University of Washington.
 *
 * The full text of our legal notices is contained in the file called
 * CPYRIGHT, included with this distribution.
 *
 *
 * USENET News reading additions in part by L Lundblade / NorthWestNet, 1993
 * lgl@nwnet.net
 */

#include "headers.h"

/* Context Manager context format digester
 * Accepts: context string and buffers for sprintf-suitable context,
 *          remote host (for display), remote context (for display), 
 *          and view string
 * Returns: NULL if successful, else error string
 *
 * Comments: OK, here's what we expect a fully qualified context to 
 *           look like:
 *
 * [*] [{host ["/"<proto>] [:port]}] [<cntxt>] "[" [<view>] "]" [<cntxt>]
 *
 *         2) It's understood that double "[" or "]" are used to 
 *            quote a literal '[' or ']' in a context name.
 *
 *         3) an empty view in context implies a view of '*', so that's 
 *            what get's put in the view string
 *
 */
char *
context_digest (context, scontext, host, rcontext, view)
    char *context, *scontext, *host, *rcontext, *view;
{
    char *p, *viewp = view, tmp[MAILTMPLEN];
    int   i = 0;

    if((p = context) == NULL || *p == '\0'){
	if(scontext)			/* so the caller can apply */
	  strcpy(scontext, "%s");	/* folder names as is.     */

	return(NULL);			/* no error, just empty context */
    }

    if(!rcontext && scontext)		/* place to collect rcontext ? */
      rcontext = tmp;

    /* find hostname if requested and exists */
    if(*p == '{' || (*p == '*' && *++p == '{')){
	for(p++; *p && *p != '}' ; p++)
	  if(host)
	    *host++ = *p;		/* copy host if requested */

	while(*p && *p != '}')		/* find end of imap host */
	  p++;

	if(*p == '\0')
	  return("Unbalanced '}'");	/* bogus. */
	else
	  p++;				/* move into next field */
    }

    for(; *p ; p++){			/* get thru context */
	if(rcontext)
	  rcontext[i++] = *p;		/* copy if requested */

	if(*p == '['){			/* done? */
	    if(*++p == '\0')		/* look ahead */
	      return("Unbalanced '['");

	    if(*p != '[')		/* not quoted: "[[" */
	      break;
	}
    }

    if(*p == '\0')
      return("No '[' in context");

    for(; *p ; p++){			/* possibly in view portion ? */
	if(*p == ']'){
	    if(*(p+1) == ']')		/* is quoted */
	      p++;
	    else
	      break;
	}

	if(viewp)
	  *viewp++ = *p;
    }

    if(*p != ']')
      return("No ']' in context");

    for(; *p ; p++){			/* trailing context ? */
	if(rcontext)
	  rcontext[i++] = *p;
    }

    if(host) *host = '\0';
    if(rcontext) rcontext[i] = '\0';
    if(viewp) {
/* MAIL_LIST: dealt with it in new_context since could be either '%' or '*'
	if(viewp == view)
	  *viewp++ = '*';
*/

	*viewp = '\0';
    }

    if(scontext){			/* sprint'able context request ? */
	if(*context == '*')
	  *scontext++ = *context++;

	if(*context == '{'){
	    while(*context && *context != '}')
	      *scontext++ = *context++;

	    *scontext++ = '}';
	}

	for(p = rcontext; *p ; p++){
	    if(*p == '[' && *(p+1) == ']'){
		*scontext++ = '%';	/* replace "[]" with "%s" */
		*scontext++ = 's';
		p++;			/* skip ']' */
	    }
	    else
	      *scontext++ = *p;
	}

	*scontext = '\0';
    }

    return(NULL);			/* no problems to report... */
}


/* Context Manager apply name to context
 * Accepts: buffer to write, context to apply, ambiguous folder name
 * Returns: buffer filled with fully qualified name in context
 *          No context applied if error
 */
char *
context_apply(b, c, n)
    char      *b;
    CONTEXT_S *c;
    char      *n;
{
    if(!c || IS_REMOTE(n) || (!IS_REMOTE(c->context) && is_absolute_path(n))){
	strcpy(b, n);				/* no context! */
    }
    else if(n[0] == '#'){
	if(IS_REMOTE(c->context)){
	    char *p = strchr(c->context, '}');	/* n specifies namespace */
	    sprintf(b, "%.*s%s", p - c->context + 1, c->context, n);
	}
	else
	  strcpy(b, n);
    }
    else if(c->dir->ref)			/* has reference string! */
      sprintf(b, "%s%s", c->dir->ref, n);
    else					/* no ref, apply to context */
      sprintf(b, c->context, n);

    return(b);
}


/* Context Manager check if name is ambiguous
 * Accepts: candidate string
 * Returns: T if ambiguous, NIL if fully-qualified
 */

int
context_isambig (s)
    char *s;
{
    return(!(*s == '{' || *s == '#'));
}

/* Context Manager create mailbox
 * Accepts: context
 *	    mail stream
 *	    mailbox name to create
 * Returns: T on success, NIL on failure
 */

long
context_create (context, stream, mailbox)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char       *mailbox;
{
  char tmp[MAILTMPLEN];		/* must be within context */

  return(cntxt_allowed(context_apply(tmp, context, mailbox))
	 ? mail_create(stream,tmp) : 0L);
}


/* Context Manager open
 * Accepts: context
 *	    candidate stream for recycling
 *	    mailbox name
 *	    open options
 * Returns: stream to use on success, NIL on failure
 */

MAILSTREAM *
context_open (context, old, name, opt)
    CONTEXT_S  *context;
    MAILSTREAM *old;
    char       *name;
    long	opt;
{
  char tmp[MAILTMPLEN];		/* build FQN from ambiguous name */

  return(cntxt_allowed(context_apply(tmp, context, name))
	  ? mail_open(old,tmp,opt) : (MAILSTREAM *)NULL);
}


/* Context Manager status
 * Accepts: context
 *	    candidate stream for recycling
 *	    mailbox name
 *	    open options
 * Returns: T if call succeeds, NIL on failure
 */

long
context_status (context, stream, name, opt)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char       *name;
    long	opt;
{
    char tmp[MAILTMPLEN];	/* build FQN from ambiguous name */

    return(cntxt_allowed(context_apply(tmp, context, name))
	     ? mail_status(stream,tmp,opt) : 0L);
}


/* Context Manager rename
 * Accepts: context
 *	    mail stream
 *	    old mailbox name
 *	    new mailbox name
 * Returns: T on success, NIL on failure
 */

long
context_rename (context, stream, old, new)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char       *old, *new;
{
    char tmp[MAILTMPLEN],tmp2[MAILTMPLEN];

    return((cntxt_allowed(context_apply(tmp, context, old))
	    && cntxt_allowed(context_apply(tmp2, context, new)))
	     ? mail_rename(stream,tmp,tmp2) : 0L);
}


/* Context Manager delete mailbox
 * Accepts: context
 *	    mail stream
 *	    mailbox name to delete
 * Returns: T on success, NIL on failure
 */

long
context_delete (context, stream, name)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char       *name;
{
    char tmp[MAILTMPLEN];		/* build FQN from ambiguous name */

    return(cntxt_allowed(context_apply(tmp, context, name))
	     ? mail_delete(stream, tmp) : 0L);
}


/* Context Manager append message string
 * Accepts: context
 *	    mail stream
 *	    destination mailbox
 *	    stringstruct of message to append
 * Returns: T on success, NIL on failure
 */

long
context_append (context, stream, name, msg)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char       *name;
    STRING     *msg;
{
    char tmp[MAILTMPLEN];	/* build FQN from ambiguous name */

    return(cntxt_allowed(context_apply(tmp, context, name))
	     ? mail_append(stream, tmp, msg) : 0L);
}


/* Context Manager append message string with flags
 * Accepts: context
 *	    mail stream
 *	    destination mailbox
 *          flags to assign message being appended
 *          date of message being appended
 *	    stringstruct of message to append
 * Returns: T on success, NIL on failure
 */

long
context_append_full (context, stream, name, flags, date, msg)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char       *name, *flags, *date;
    STRING     *msg;
{
    char tmp[MAILTMPLEN];		/* build FQN from ambiguous name */

    return(cntxt_allowed(context_apply(tmp, context, name))
	     ? mail_append_full(stream, tmp, flags, date, msg) : 0L);
}


/* Mail copy message(s)
 * Accepts: context
 *	    mail stream
 *	    sequence
 *	    destination mailbox
 */

long
context_copy (context, stream, sequence, name)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char *sequence;
    char *name;
{
    char *s, tmp[MAILTMPLEN];		/* build FQN from ambiguous name */

    if(context_apply(tmp, context, name)[0] == '{'){
	if(s = strindex(tmp, '}'))
	  s++;
	else
	  return(0L);
    }
    else
      s = tmp;

    if(!*s)
      strcpy(s = tmp, "INBOX");		/* presume "inbox" ala c-client */

    return(cntxt_allowed(s) ? mail_copy(stream, sequence, s) : 0L);
}


/*
 * Context manager stream usefulness test
 * Accepts: context
 *	    mail name
 *	    mailbox name
 *	    mail stream to test against mailbox name
 * Returns: stream if useful, else NIL
 */
MAILSTREAM *
context_same_stream(context, name, stream)
    CONTEXT_S  *context;
    MAILSTREAM *stream;
    char       *name;
{
    extern MAILSTREAM *same_stream();
    char tmp[MAILTMPLEN];		/* build FQN from ambiguous name */

    return(same_stream(context_apply(tmp, context, name), stream));
}


#ifdef NEWBB

/*  This is code for inclusion in the c-client to support the command
    to discover all the new newly created news groups, since the last
    time the subscription screen was viewed. 

    Some of this code goes un nntpunx.c and one line in nntpunx.h

    It's not clear whether or not this code will be included as part
    of the official c-client distribution so it's included here.
*/

#ifdef INCLUDE_THIS_CODE_AT_THE_END_OF_NNTPUNX_C

/* NNTP mail find list of all bboards
 * Accepts: mail stream
 *	    pattern to search
 *          last time that new groups were checked for in NNTP format
 */

void nntp_find_new_bboard (stream, pat, lasttime)
    MAILSTREAM *stream;
    char       *pat, *lasttime;
{
  char *s,*t,*bbd,*patx,tmp[MAILTMPLEN], checktime[20];
				/* use .newsrc if a stream given */
  if (stream && LOCAL && LOCAL->nntpstream) {
				/* begin with a host specification? */
    if (((*pat == '{') || ((*pat == '*') && (pat[1] == '{'))) &&
	(t = strchr (pat,'}')) && *(patx = ++t)) {
      if (*pat == '*') pat++;	/* yes, skip leading * (old Pine behavior) */
      strcpy (tmp,pat);		/* copy host name */
      bbd = tmp + (patx - pat);	/* where we write the bboards */
    }
    else {			/* no host specification */
      bbd = tmp;		/* no prefix */
      patx = pat;		/* use entire specification */
    }
				/* ask server for all active newsgroups */
    if (!(smtp_send (LOCAL->nntpstream,"NEWGROUPS",lasttime) == NNTPNEWLIST))
      return;
				/* process data until we see final dot */
    while ((s = tcp_getline (LOCAL->nntpstream->tcpstream)) && *s != '.') {
				/* tie off after newsgroup name */
      if (t = strchr (s,' ')) *t = '\0';
      if (pmatch (s,patx)) {	/* report to main program if have match */
	strcpy (bbd,s);		/* write newsgroup name after prefix */
	mm_bboard(tmp);
      }
      fs_give ((void **) &s);	/* clean up */
    }
  }
}

     
#endif


#ifdef INCLUDE_THIS_CODE_NEAR_THE_TOP_OF_NNTPUNX_H

#define NNTPNEWLIST (long) 231  /* NNTP list of new news groups */

#endif

#endif /* NEWBB */
