text: /*
   * ========================================================================
   * Copyright 2013-2021 Eduardo Chappa
   * Copyright 2006-2007 University of Washington
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * ========================================================================
   */
  
  #include "../pith/headers.h"
  #include "../pith/keyword.h"
  #include "../pith/state.h"
  #include "../pith/flag.h"
  #include "../pith/string.h"
  #include "../pith/status.h"
  #include "../pith/util.h"
  
  
  /*
   * Internal prototypes
   */
  
  
  /*
   * Read the keywords array into a KEYWORD_S structure.
   * Make sure that all of the strings are UTF-8.
   */
  KEYWORD_S *
  init_keyword_list(char **keywordarray)
  {
      char     **t, *nickname, *keyword;
      KEYWORD_S *head = NULL, **tail;
  
      tail = &head;
      for(t = keywordarray; t && *t && **t; t++){
  	nickname = keyword = NULL;
  	get_pair(*t, &nickname, &keyword, 0, 0);
  	*tail = new_keyword_s(keyword, nickname);
  	tail  = &(*tail)->next;
  
  	if(keyword)
  	  fs_give((void **) &keyword);
  
  	if(nickname)
  	  fs_give((void **) &nickname);
      }
  
      return(head);
  }
  
  
  KEYWORD_S *
  new_keyword_s(char *keyword, char *nickname)
  {
      KEYWORD_S *kw = NULL;
  
      kw = (KEYWORD_S *) fs_get(sizeof(*kw));
      memset(kw, 0, sizeof(*kw));
  
      if(keyword && *keyword)
        kw->kw = cpystr(keyword);
  
      if(nickname && *nickname)
        kw->nick = cpystr(nickname);
      
      return(kw);
  }
  
  
  void
  free_keyword_list(KEYWORD_S **kl)
  {
      if(kl && *kl){
  	if((*kl)->next)
  	  free_keyword_list(&(*kl)->next);
  
  	if((*kl)->kw)
  	  fs_give((void **) &(*kl)->kw);
  
  	if((*kl)->nick)
  	  fs_give((void **) &(*kl)->nick);
  
  	fs_give((void **) kl);
      }
  }
  
  
  /*
   * Return a pointer to the keyword associated with a nickname, or the
   * input itself if no match.
   */
  char *
  nick_to_keyword(char *nick)
  {
      KEYWORD_S *kw;
      char      *ret;
  
      ret = nick;
      for(kw = ps_global->keywords; kw; kw = kw->next)
        if(!strcmp(nick, kw->nick ? kw->nick : kw->kw ? kw->kw : "")){
  	  if(kw->nick)
  	    ret = kw->kw;
  
  	  break;
        }
      
      return(ret);
  }
  
  
  /*
   * Return a pointer to the nickname associated with a keyword, or the
   * input itself if no match.
   */
  char *
  keyword_to_nick(char *keyword)
  {
      KEYWORD_S *kw;
      char      *ret;
  
      ret = keyword;
      for(kw = ps_global->keywords; kw; kw = kw->next)
        if(!strcmp(keyword, kw->kw ? kw->kw : "")){
  	  if(kw->nick)
  	    ret = kw->nick;
  
  	  break;
        }
      
      return(ret);
  }
  
  
  /*
   * Return a pointer to the keyword associated with an initial, or the
   * input itself if no match.
   * Only works for ascii initials.
   */
  char *
  initial_to_keyword(char *initial)
  {
      KEYWORD_S *kw;
      char      *ret;
      int        init;
      int        kwinit = 0;
  
      ret = initial;
      if(initial[0] && initial[1] == '\0'){
  	init = initial[0];
  	if(isascii(init) && isupper(init))
  	  init = tolower((unsigned char) init);
  
  	for(kw = ps_global->keywords; kw; kw = kw->next){
  	    if(kw->nick)
  	      kwinit = kw->nick[0];
  	    else if(kw->kw)
  	      kwinit = kw->kw[0];
  
  	    if(isascii(kwinit) && isupper(kwinit))
  	      kwinit = tolower((unsigned char) kwinit);
  
  	    if(kwinit == init){
  		ret = kw->kw;
  		break;
  	    }
  	}
      }
      
      return(ret);
  }
  
  
  int
  user_flag_is_set(MAILSTREAM *stream, long unsigned int rawno, char *keyword)
  {
      int           j, is_set = 0;
      MESSAGECACHE *mc;
  
      if(stream && keyword && keyword[0]){
  	if(rawno > 0L && stream
  	   && rawno <= stream->nmsgs
  	   && (mc = mail_elt(stream, rawno)) != NULL){
  	    j = user_flag_index(stream, keyword);
  	    if(j >= 0 && j < NUSERFLAGS && ((1 << j) & mc->user_flags))
  	      is_set++;
  	}
      }
  	
      return(is_set);
  }
  
  
  /*
   * Returns the bit position of the keyword in stream, else -1.
   */
  int
  user_flag_index(MAILSTREAM *stream, char *keyword)
  {
      int i, retval = -1;
      char *p;
  
      if(stream && keyword && keyword[0]){
  	for(i = 0; i < NUSERFLAGS; i++)
  	  if((p=stream_to_user_flag_name(stream, i)) && !strucmp(keyword, p)){
  	      retval = i;
  	      break;
  	  }
      }
  
      return(retval);
  }
  
  
  char *
  stream_to_user_flag_name(MAILSTREAM *stream, int k)
  {
      if(stream && stream->user_flags && k >= 0 && k < NUSERFLAGS)
        return(stream->user_flags[k]);
  
      return(NULL);
  }
  
  
  int
  some_user_flags_defined(MAILSTREAM *stream)
  {
      int k;
  
      if(stream)
        for(k = 0; k < NUSERFLAGS; k++)
  	if(stream_to_user_flag_name(stream, k))
  	  return 1;
  
      return 0;
  }
  
  
  /*----------------------------------------------------------------------
    Build flags string based on requested flags and what's set in messagecache
  
     Args: flags -- flags to test
  
   Result: allocated, space-delimited flags string is returned
   ----*/
  char *
  flag_string(MAILSTREAM *stream, long rawno, long int flags)
  {
      MESSAGECACHE *mc;
      char *p, *q, *returned_flags = NULL;
      size_t len = 0;
      int k;
  
      mc = (rawno > 0L && stream && rawno <= stream->nmsgs)
  			    ? mail_elt(stream, rawno) : NULL;
      if(!mc)
        return returned_flags;
  
      if((flags & F_DEL) && mc->deleted)
        len += strlen("\\DELETED") + 1;
  
      if((flags & F_ANS) && mc->answered)
        len += strlen("\\ANSWERED") + 1;
  
      if((flags & F_FWD) && user_flag_is_set(stream, rawno, FORWARDED_FLAG))
        len += strlen(FORWARDED_FLAG) + 1;
  
      if((flags & F_FLAG) && mc->flagged)
        len += strlen("\\FLAGGED") + 1;
  
      if((flags & F_SEEN) && mc->seen)
        len += strlen("\\SEEN") + 1;
  
      if((flags & F_KEYWORD) && stream->user_flags){
  	for(k = 0; k < NUSERFLAGS; k++){
  	    if((q=stream_to_user_flag_name(stream, k))
  	       && user_flag_is_set(stream, rawno, q)){
  		len += strlen(q) + 1;
  	    }
  	}
      }
  
      returned_flags = (char *) fs_get((len+1) * sizeof(char));
      p = returned_flags;
      *p = '\0';
  
      if((flags & F_DEL) && mc->deleted)
        sstrncpy(&p, "\\DELETED ", len+1-(p-returned_flags));
  
      if((flags & F_ANS) && mc->answered)
        sstrncpy(&p, "\\ANSWERED ", len+1-(p-returned_flags));
  
      if((flags & F_FWD) && user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
  	sstrncpy(&p, FORWARDED_FLAG, len+1-(p-returned_flags));
  	sstrncpy(&p, " ", len+1-(p-returned_flags));
      }
  
      if((flags & F_FLAG) && mc->flagged)
        sstrncpy(&p, "\\FLAGGED ", len+1-(p-returned_flags));
  
      if((flags & F_SEEN) && mc->seen)
        sstrncpy(&p, "\\SEEN ", len+1-(p-returned_flags));
  
      if((flags & F_KEYWORD) && stream->user_flags){
  	for(k = 0; k < NUSERFLAGS; k++){
  	    if((q=stream_to_user_flag_name(stream, k))
  	       && user_flag_is_set(stream, rawno, q)){
  		sstrncpy(&p, q, len+1-(p-returned_flags));
  		sstrncpy(&p, " ", len+1-(p-returned_flags));
  		len += strlen(p) + 1;
  	    }
  	}
      }
  
      if(p != returned_flags && (len+1-(p-returned_flags)>0))
        *--p = '\0';
  
      return returned_flags;
  }
  
  
  long
  get_msgno_by_msg_id(MAILSTREAM *stream, char *message_id, MSGNO_S *msgmap)
  {
      SEARCHPGM  *pgm = NULL;
      long        hint = mn_m2raw(msgmap, mn_get_cur(msgmap));
      long        newmsgno = -1L;
      int         iter = 0;
      MESSAGECACHE *mc;
      extern MAILSTREAM *mm_search_stream;
      extern long        mm_search_count;
  
      if(!(message_id && message_id[0]) || stream->nmsgs < 1L)
        return(newmsgno);
  
      mm_search_count = 0L;
      mm_search_stream = stream;
      while(mm_search_count == 0L && iter++ < 3
  	  && (pgm = mail_newsearchpgm()) != NULL){
  	pgm->message_id = mail_newstringlist();
  	pgm->message_id->text.data = (unsigned char *) cpystr(message_id);
  	pgm->message_id->text.size = strlen(message_id);
  
  	if(iter > 1 || hint > stream->nmsgs)
  	  iter++;
  
  	if(iter == 1){
  	    /* restrict to hint message on first try */
  	    pgm->msgno = mail_newsearchset();
  	    pgm->msgno->first = pgm->msgno->last = hint;
  	}
  	else if(iter == 2){
  	    /* restrict to last 50 messages on 2nd try */
  	    pgm->msgno = mail_newsearchset();
  	    if(stream->nmsgs > 100L)
  	      pgm->msgno->first = stream->nmsgs-50L;
  	    else{
  		pgm->msgno->first = 1L;
  		iter++;
  	    }
  
  	    pgm->msgno->last = stream->nmsgs;
  	}
  
  	pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
  
  	if(mm_search_count){
  	    for(newmsgno=stream->nmsgs; newmsgno > 0L; newmsgno--)
  	      if((mc = mail_elt(stream, newmsgno)) && mc->searched)
  		break;
  	}
      }
  
      return(mn_raw2m(msgmap, newmsgno));
  }
  
  
  /*
   * These chars are not allowed in keywords.
   *
   * Returns 0 if ok, 1 if not.
   * Returns an allocated error message on error.
   */
  int
  keyword_check(char *kw, char **error)
  {
      register char *t;
      char buf[100], *p;
  
      if(!kw || !kw[0])
        return 1;
  
      kw = nick_to_keyword(kw);
  
      for(p = kw; p && *p; p++)
        if(!isascii(*p)){
  	if(error)
  	  *error = cpystr("Keywords must be all ASCII characters");
  
  	return 1;
        }
  
      if((t = strindex(kw, SPACE)) ||
         (t = strindex(kw, '{'))   ||
         (t = strindex(kw, '('))   ||
         (t = strindex(kw, ')'))   ||
         (t = strindex(kw, ']'))   ||
         (t = strindex(kw, '%'))   ||
         (t = strindex(kw, '"'))   ||
         (t = strindex(kw, '\\'))  ||
         (t = strindex(kw, '*'))){
  	char s[4];
  	s[0] = '"';
  	s[1] = *t;
  	s[2] = '"';
  	s[3] = '\0';
  	if(error){
  	    snprintf(buf, sizeof(buf), "%s not allowed in keywords",
  		*t == SPACE ?
  		    "Spaces" :
  		    *t == '"' ?
  			"Quotes" :
  			*t == '%' ?
  			    "Percents" :
  			    s);
  	    *error = cpystr(buf);
  	}
  
  	return 1;
      }
  
      return 0;
  }
  