#include "args.h"

#include "config.h"
/*
 * Copyright (c) 1986, 2014 by The Trustees of Columbia University in
 * the City of New York.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  + Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  + Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 *  + Neither the name of Columbia University nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 */

#ifndef lint
static const char *rcsid = "$Header: /usr/local/src/mm/mm-0.94/mm/RCS/keywords.c,v 1.2 2005/05/28 22:39:23 beebe Exp $";
#endif

#include "mm.h"
#include "parse.h"
#include "message.h"
#include "rd.h"

extern keylist user_keywords;
extern mail_msg *current;
static char *key_validate ARGS((const char *str));
static const char *parse_current_keywords ARGS((void));

#if 0					/* no longer used */
static const char *parse_keywords ARGS((keytab *kt));
#endif

static const char *parse_new_keywords ARGS((void));
static int keycmp ARGS((char **a, char **b));
static keytab *mk_keyword_keytab ARGS((keylist k1, keylist k2));
static void sort_keys ARGS((keylist kl));

keylist
#if HAVE_STDC
free_keylist(keylist kl)
#else /* K&R style */
free_keylist(kl)
keylist kl;
#endif /* HAVE_STDC */
{
    keylist fk;

    if (kl) {
	for(fk = kl; *fk != nil; fk++)
	    free(*fk);
	free(kl);
	kl = nil;
    }
    return(nil);
}

keylist
#if HAVE_STDC
add_keyword(const char *str, keylist kl)
#else /* K&R style */
add_keyword(str,kl)
const char *str;
keylist kl;
#endif /* HAVE_STDC */
{
    int count;
    char *cp;
    if (!lookup_keyword(str,kl)) {
	cp = key_validate(str);
	if (cp == NULL || *cp == '\0')
	    return(kl);
	for(count = 0; kl && kl[count]; count++);
	kl = (keylist) safe_realloc(kl, (2+count)*sizeof(char *));
	kl[count++] = safe_strcpy(cp);
	kl[count] = nil;
    }
    return(kl);
}

keylist
#if HAVE_STDC
rem_keyword(const char *str, keylist kl)
#else /* K&R style */
rem_keyword(str, kl)
const char *str;
keylist kl;
#endif /* HAVE_STDC */
{
    keylist fk;

    if (strcmp(str,"*") == 0 && kl) {
	free(kl);
	return(nil);
    }
    for(fk = kl; fk && *fk != nil; fk++)
	if (ustrcmp(*fk, str) == 0)
	    break;
    if (fk && *fk) {
	free(*fk);
	do {
	    *fk = *(fk+1);
	    fk++;
	} while (*fk);

	if (kl[0] == nil) {
	    free(kl);
	    return(nil);
	}
    }
    return(kl);
}

int
#if HAVE_STDC
lookup_keyword(const char *str, keylist kl)
#else /* K&R style */
lookup_keyword(str, kl)
const char *str;
keylist kl;
#endif /* HAVE_STDC */
{
    keylist fk;

    if (strcmp(str,"*") == 0 && kl)
	return(true);
    for(fk = kl; fk && *fk != nil; fk++)
	if (ustrcmp(*fk, str) == 0)
	    return(true);
    return(false);
}

static keytab *
#if HAVE_STDC
mk_keyword_keytab(keylist k1, keylist k2)
#else /* K&R style */
mk_keyword_keytab(k1, k2)
keylist k1,k2;
#endif /* HAVE_STDC */
{
    keylist k,keys=nil;

    for(k = k1; k && *k; k++)
	keys = add_keyword(*k,keys);
    for(k = k2; k && *k; k++)
	keys = add_keyword(*k,keys);
    if (keys != nil)
	sort_keys(keys);
    return(keylist_to_keytab(keys));
}

static int
#if HAVE_STDC
keycmp(char **a, char **b)
#else /* K&R style */
keycmp(a,b)
char **a,**b;
#endif /* HAVE_STDC */
{
    return(strcmp(*a,*b));
}

static void
#if HAVE_STDC
sort_keys(keylist kl)
#else /* K&R style */
sort_keys(kl)
keylist kl;
#endif /* HAVE_STDC */
{
    int i;
    keylist k;

    if (kl == nil)
	return;
    for(i = 0, k = kl; *k; k++, i++);
    qsort(kl, i, sizeof(char *), (QSORT_FUN_TYPE)keycmp);
    return;
}


keytab *
#if HAVE_STDC
keylist_to_keytab(keylist kl)
#else /* K&R style */
keylist_to_keytab(kl)
keylist kl;
#endif /* HAVE_STDC */
{
    keylist k;
    static keywrd *keys = nil;
    static keytab tab;
    int i,len;

    for(len = 0, k = kl; k && *k; k++, len++);
    if (keys != nil) {
	free(keys);
    }
    if (len > 0)
	keys = (keywrd *)malloc(len * sizeof(keywrd));
    else keys = nil;
    for(i = 0; i < len; i++) {
	keys[i]._kwkwd = kl[i];
	keys[i]._kwflg = 0;
	keys[i]._kwval = i;
    }
    tab._ktcnt = len;
    tab._ktwds = keys;
    return(&tab);
}


static brktab keybrk = {
    {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f,
	0x80, 0x00, 0x00, 0x1e, 0x80, 0x00, 0x00, 0x1f,
    },
    {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f,
	0x80, 0x00, 0x00, 0x1e, 0x80, 0x00, 0x00, 0x1f,
    },
};

static brktab fldbrk = {
    {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x3f,
	0x80, 0x00, 0x00, 0x1e, 0x80, 0x00, 0x00, 0x1f,
    },
    {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x00, 0x3f,
	0x80, 0x00, 0x00, 0x1e, 0x80, 0x00, 0x00, 0x1f,
    },
};

#if 0					/* no longer used */
static const char *
parse_keywords(kt)
keytab *kt;
{
    static fdb keys = { _CMKEY, 0, nil, nil, nil, nil, &keybrk };
    static fdb astfdb = { _CMTOK, CM_SDH, nil, (pdat) "*" };
    keys._cmdat = (pdat) kt;
    parse(fdbchn(&astfdb,&keys,nil), &pv, &used);
    if (used == &keys)
	return(kt->_ktwds[pv._pvkey]._kwkwd);
    else
	return("*");
}
#endif

static const char *
parse_new_keywords(VOID)
{
    keytab *kt = mk_keyword_keytab(cf?cf->keywords:nil,user_keywords);
    static fdb keys = { _CMKEY, 0, nil, nil, nil, nil, &keybrk };
    static fdb fldfdb = { _CMFLD, CM_SDH, nil, nil, "New Keyword", nil,
			      &fldbrk };

    keys._cmdat = (pdat) kt;
    parse(fdbchn(&keys,&fldfdb,nil), &pv, &used);
    if (used == &keys)
	return(kt->_ktwds[pv._pvkey]._kwkwd);
    else
	return(atmbuf);
}

const char *
parse_old_keywords(VOID)
{
    keytab *kt = mk_keyword_keytab(cf?cf->keywords:nil,(keylist)nil);
    static fdb keys = { _CMKEY, 0, nil, nil, nil, nil, &keybrk };
    static fdb astfdb = { _CMTOK, 0, nil, (pdat) "*" };

    keys._cmdat = (pdat) kt;
    parse(fdbchn(&keys,&astfdb,nil), &pv, &used);
    if (used == &keys)
	return(kt->_ktwds[pv._pvkey]._kwkwd);
    else
	return("*");
}


void
#if HAVE_STDC
cmd_keyword(int n)
#else /* K&R style */
cmd_keyword(n)
int n;
#endif /* HAVE_STDC */
{
    if (mode & MM_SEND) {
	char *key;

	noise("on outgoing message");
	key = (safe_strcpy(parse_new_keywords()));
	confirm();
	outgoing_keyword(key);
    }
    else {
	do_keyword();
    }
}

void
do_keyword(VOID) {
    message *m;
    char *key;

    if (!check_cf(O_RDWR))		/* pre-check file existence */
	return;
    key = (safe_strcpy(parse_new_keywords()));
    if (!parse_sequence ("current",NULL,NULL)) {
	if (!check_cf(O_WRONLY))	/* need write permission */
	    return;
	m = &cf->msgs[cf->current];
	m->keywords = add_keyword(key,m->keywords);
	set_msg_keywords(m);
	cf->keywords = add_keyword(key,cf->keywords);
	m->flags |= M_MODIFIED;
	(*msg_ops[cf->type].wr_msg)(cf,m,cf->current,0);
    }
    else {
	int n;
	if (!check_cf(O_WRONLY))	/* need write permission */
	    return;
	for (n = sequence_start (cf->sequence); n;
	     n = sequence_next (cf->sequence)) {
	    m = &cf->msgs[cf->current];
	    m->keywords = add_keyword(key,m->keywords);
	    set_msg_keywords(m);
	    cf->keywords = add_keyword(key,cf->keywords);
	    m->flags |= M_MODIFIED;
	    (*msg_ops[cf->type].wr_msg)(cf,m,cf->current,0);
	}
	seq_print (true);
    }
    free(key);
}

void
#if HAVE_STDC
cmd_unkeyword(int n)
#else /* K&R style */
cmd_unkeyword(n)
int n;
#endif /* HAVE_STDC */
{
    char *key;

    if (mode & MM_SEND) {
	noise("from outgoing message");
	key = (safe_strcpy(parse_current_keywords()));
	confirm();
	unoutgoing_keyword(key);
    }
    else {
	message *m;
	if (!check_cf(O_RDWR))		/* going to try and modify things */
	    return;
	key = (safe_strcpy(parse_old_keywords()));
	if (!parse_sequence ("current",NULL,NULL)) {
	    if (!check_cf(O_WRONLY))	/* need write permission */
		return;
	    m = &cf->msgs[cf->current];
	    m->keywords = rem_keyword(key,m->keywords);
	    set_msg_keywords(m);
	    m->flags |= M_MODIFIED;
	    (*msg_ops[cf->type].wr_msg)(cf,m,cf->current,0);
	}
	else {
	    int k;
	    if (!check_cf(O_WRONLY))	/* need write permission */
		return;
	    for (k = sequence_start (cf->sequence); k;
		 k = sequence_next (cf->sequence)) {
		m = &cf->msgs[cf->current];
		m->keywords = rem_keyword(key,m->keywords);
		set_msg_keywords(m);
		m->flags |= M_MODIFIED;
		(*msg_ops[cf->type].wr_msg)(cf,m,cf->current,0);
	    }
	    seq_print (true);
	}
    }
    free(key);
}

void
#if HAVE_STDC
set_msg_keywords(message *m)
#else /* K&R style */
set_msg_keywords(m)
message *m;
#endif /* HAVE_STDC */
{
    char *begin = m->text;
    char *oldkey = hfind("keywords",begin);
    char *newkey=nil;
    char *end=nil;
    keylist k;
    int len=0;
    char *newmsg;

    if (oldkey)
	end = skipheader(oldkey);	/* point to next header */
    else {				/* make it the last header */
	if (m->keywords == nil)
	    return;
	oldkey = search("\n\n",m->text);
	if (oldkey == nil)
	    oldkey = end = begin;
	else
	    end = ++oldkey;		/* move past one '\n' */
    }

    if (m->keywords) {			/* create the new keywords: field */

	for(k = m->keywords; *k; k++)
	    len += strlen(*k) + ((*(k+1)) ? 2 : 0); /* new keyword + ", " */

	newkey = (char*)malloc(len + 2 + strlen("Keywords: "));

	strcpy(newkey,"Keywords: ");
	for(k = m->keywords; *k; k++) {
	    strcat(newkey,*k);
	    if (*(k+1))
		strcat(newkey,", ");
	}
	strcat(newkey,"\n");
    }
    len = (oldkey - begin);
    if (newkey) len += strlen(newkey);
    if (end) len += strlen(end);
    newmsg = (char*)malloc(len+1);
    strncpy(newmsg,begin,oldkey-begin);
    newmsg[oldkey-begin] = '\0';
    if (newkey) {
	strcat(newmsg,newkey);
	free(newkey);
    }
    if (end)
	strcat(newmsg,end);
    free(m->text);
    m->text = newmsg;
    m->size = len;
}

static const char *
parse_current_keywords(VOID)
{
    keytab *kt;
    static fdb keys = { _CMKEY };
    static fdb astfdb = { _CMTOK, 0, nil, (pdat) "*" };
    int flag;

    flag = (current->keywords && current->keywords->keys);
    kt = mk_keyword_keytab(flag ? current->keywords->keys : nil ,(keylist)nil);
    keys._cmdat = (pdat) kt;
    parse(fdbchn(&keys,&astfdb,nil), &pv, &used);
    if (used == &keys)
	return(kt->_ktwds[pv._pvkey]._kwkwd);
    else
	return("*");
}

#define cf cf_			/* avoid warnings about shadowed global */

void
#if HAVE_STDC
get_incoming_keywords(msgvec *cf, message *m)
#else /* K&R style */
get_incoming_keywords(cf,m)
msgvec *cf;
message *m;
#endif /* HAVE_STDC */
{
    char *k = htext("keywords", m->text);
    char *c1, *c2;

    c2 = k;
    if (k == nil)
	return;
    k = stripspaces(k);
    while((c1 = index(k,',')) != nil) {
	*c1 = '\0';
	while(*k == ' ') k++;
	m->keywords = add_keyword(k,m->keywords);
	if (cf)				/* sendmail only wants message done */
	    cf->keywords = add_keyword(k,cf->keywords);
	*c1 = ',';
	k = c1 + 1;
    }
    while(*k == ' ') k++;
    if (*k) {
	m->keywords = add_keyword(k,m->keywords);
	if (cf)
	    cf->keywords = add_keyword(k,cf->keywords);
    }
    free(c2);
}

keylist
#if HAVE_STDC
match_keylist(char *s)
#else /* K&R style */
match_keylist(s)
char *s;
#endif /* HAVE_STDC */
{
    char *c1;
    keylist kl=nil;

    s = stripspaces(s);
    while((c1 = index(s,',')) != nil) {
	*c1 = '\0';
	while(*s == ' ') s++;
	kl = add_keyword(s,kl);
	*c1 = ',';
	s = c1 + 1;
    }
    while(*s == ' ') s++;
    if (*s) {
	kl = add_keyword(s, kl);
    }
    return(kl);
}

static char *
#if HAVE_STDC
key_validate(const char *str)
#else /* K&R style */
key_validate(str)
const char *str;
#endif /* HAVE_STDC */
{
    static char buf[100];
    int i;

    strcpy(buf,str);
    for(i = strlen(buf) - 1; i>= 0; i--)
	if (isspace(buf[i]))
	    buf[i] = '\0';
	else
	    break;

    for(i = 0; i < (int)strlen(buf); i++)
	if (!isspace(buf[i]))
	    break;
    if (i > 0)
	bcopy(&buf[i], buf, strlen(&buf[i])+1);
    for(i = 0; i < (int)strlen(buf); i++) {
	if (isspace(buf[i])){
	    buf[i] = '_';
	}
	else if (!isprint(buf[i])) {
	    buf[i] = 'X';
	}
    }
    return(buf);
}



/*
 * keylist_copy:
 * take a keylist and malloc up a copy
 */

keylist
#if HAVE_STDC
keylist_copy (keylist kl)
#else /* K&R style */
keylist_copy (kl)
keylist kl;
#endif /* HAVE_STDC */
{
    int i = 0;
    keylist nk, k;

    for (k = kl; *k != nil; k++)
	i++;
    nk = (keylist) malloc ((i+1)*sizeof (char *));
    for (i = 0; kl[i] != nil; i++)
	nk[i] = safe_strcpy (kl[i]);
    nk[i] = nil;
    return (nk);
}
