/* $Cambridge: hermes/src/prayer/session/filter.c,v 1.4 2011/06/15 14:43:47 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

#include "prayer_session.h"

/* Class for manipulating filter rules which will be converted into Exim
 * filter file */

/* filter_alloc() ********************************************************
 *
 * Allocate a new filter object. (as NIL pool: subsystem does own memory
 * managment).
 ************************************************************************/

struct filter *filter_alloc(void)
{
    struct filter *result = pool_alloc(NIL, sizeof(struct filter));

    result->type = FILTER_UNKNOWN;
    result->local_part = NIL;
    result->domain = NIL;
    result->subject = NIL;
    result->mailbox = NIL;
    result->copy = NIL;

    return (result);
}

/* filter_free() *********************************************************
 *
 * Free a filter object.
 ************************************************************************/

void filter_free(struct filter *filter)
{
    if (filter->local_part)
        free(filter->local_part);

    if (filter->domain)
        free(filter->domain);

    if (filter->subject)
        free(filter->subject);

    if (filter->mailbox)
        free(filter->mailbox);

    free(filter);
}

/* ====================================================================== */

/* Auxillary routine */

static char *maybe_strdup(char *s)
{
    char *result;

    if (s == NIL)
        return (NIL);

    if ((result = strdup(s)))
        return (result);

    fatal("Out of memory");
    /* NOTREACHED */
    return (NIL);
}

/* filter_set_type() *****************************************************
 *
 * Set type of filter
 ************************************************************************/

void filter_set_type(struct filter *filter, FILTER_TYPE type)
{
    filter->type = type;
}

/* filter_set_local_part() ***********************************************
 *
 * Set local_part of filter
 ************************************************************************/

void filter_set_local_part(struct filter *filter, char *local_part)
{
    filter->local_part = maybe_strdup(local_part);
}

/* filter_set_domain() ***************************************************
 *
 * Set domain of filter
 ************************************************************************/

void filter_set_domain(struct filter *filter, char *domain)
{
    filter->domain = maybe_strdup(domain);
}

/* filter_set_subject() **************************************************
 *
 * Set subject of filter
 ************************************************************************/

void filter_set_subject(struct filter *filter, char *subject)
{
    filter->subject = maybe_strdup(subject);
}

/* filter_set_mailbox() **************************************************
 *
 * Set mailbox of filter
 ************************************************************************/

void filter_set_mailbox(struct filter *filter, char *mailbox)
{
    filter->mailbox = maybe_strdup(mailbox);
}

/* filter_set_copy() *****************************************************
 *
 * Set copy of filter
 ************************************************************************/

void filter_set_copy(struct filter *filter, BOOL copy)
{
    filter->copy = copy;
}

/* ====================================================================== */

/* filter_set_addr() *****************************************************
 *
 * Set source address up into local_part and domain components, strdups
 * these components and assigns to filter object.
 *     filter:
 *    session: Used for default_domain
 *       text: Address to chop up
 ************************************************************************/

BOOL
filter_set_addr(struct filter *filter, struct session *session, char *text)
{
    struct config *config = session->config;
    struct prefs *prefs = session->options->prefs;
    struct request *request = session->request;
    ADDRESS *addr = NIL;

    if (!(addr=addr_parse(request->pool, text, ""))) {
        session_message(session, "Invalid filter: %s", ml_errmsg());
        return (NIL);
    }

    if ((addr->next) || (addr->personal && addr->personal[0])) {
        mail_free_address(&addr);
        session_message(session, "Filter must be simple, single address");
        return (NIL);
    }

    filter->local_part = pool_strdup(NIL, addr->mailbox);

    if (addr->host && addr->host[0])
        filter->domain = pool_strdup(NIL, addr->host);
    else if (config->filter_domain_pattern)
        filter->domain = pool_strdup(NIL, config->filter_domain_pattern);
    else
        filter->domain = pool_strdup(NIL, prefs->default_domain);

    mail_free_address(&addr);
    return (T);
}

/* filter_test_addr() ****************************************************
 *
 * Check whether address/pattern is valid.
 *     filter:
 *    session: Used for default_domain
 *       text: Address to chop up
 ************************************************************************/

BOOL
filter_test_addr(struct filter * filter, struct session * session,
                 char *text)
{
    struct config *config = session->config;
    struct options *options = session->options;
    struct prefs *prefs = options->prefs;
    struct request *request = session->request;
    struct list_item *li;
    ADDRESS *addr = NIL;

    if (!(addr=addr_parse(request->pool, text, ""))) {
        session_message(session, "Invalid filter: %s", ml_errmsg());
        return (NIL);
    }

    if ((addr->next) || (addr->personal && addr->personal[0])) {
        mail_free_address(&addr);
        session_message(session, "Filter must be simple, single address");
        return (NIL);
    }

    if (addr->host && addr->host[0]) {
        mail_free_address(&addr);
        return (T);
    }

    mail_free_address(&addr);

    /* Account name filters allowed iff local domains defined */
    if (config->local_domain_list == NIL) {
        session_message(session, "Invalid filter: missing domain name");
        return (NIL);
    }

    /* Look for passwd map entries for current default domain */
    for (li = config->local_domain_list->head; li; li = li->next) {
        struct config_local_domain *cld =
            (struct config_local_domain *) li;

        if (cld->cdb_map && !strcmp(prefs->default_domain, cld->name)) {
            char *value = NIL;
            BOOL rc = cdb_find(cld->cdb_map, text, strlen(text), &value);

            if (value)
                free(value);

            if (rc)
                return (T);

            session_message(session,
                            "Filter doesn't match \"%s\" account",
                            cld->name);
            return (NIL);
        }
    }

    /* Account name filters allowed iff local domains defined */
    session_message(session, "Invalid filter: missing domain name");
    return (NIL);
}

/* ====================================================================== */

/* filter_print() ********************************************************
 *
 * Convert filter into MSforward format string
 *    filter:
 *         b: Target buffer
 ************************************************************************/

void filter_print(struct filter *filter, struct buffer *b)
{
    switch (filter->type) {
    case FILTER_SENDER:
        bputs(b, "sender\n");
        break;
    case FILTER_RECIPIENT:
        bputs(b, "recip\n");
        break;
    case FILTER_SUBJECT:
        bputs(b, "subject\n");
        break;
    case FILTER_BLOCK:
        bputs(b, "block\n");
        break;
    default:
        /* Need any error handling */
        break;
    }

    if (filter->copy)
        bprintf(b, "   copy\ttrue\n");

    if (filter->domain)
        bprintf(b, "   domain\t%s\n", filter->domain);

    if (filter->local_part)
        bprintf(b, "   local_part\t%s\n", filter->local_part);

    if (filter->mailbox)
        bprintf(b, "   mailbox\t%s\n", filter->mailbox);

    if (filter->subject)
        bprintf(b, "   subject\t%s\n", filter->subject);
}

/* ====================================================================== */

/* Small utility routines for filter_sieve_print */

/* Look for '?' and '*' wildcards in local_part and domain */

static BOOL
filter_address_has_wildcard(struct filter *filter)
{
    if (filter->local_part &&
        (strchr(filter->local_part, '?') || strchr(filter->local_part, '*')))
        return(T);

    if (filter->domain &&
        (strchr(filter->domain, '?') || strchr(filter->domain, '*')))
        return(T);

    return(NIL);
}

/* Quote " chars */

static void
filter_sieve_print_quote(struct buffer *b, char *s)  
{
    char c;

    while ((c=*s++)) {
        if (c == '\"')
            bputc(b, '\\');
        bputc(b, c);
    }
}

static void
filter_sieve_print_target(struct filter *filter, struct buffer *b)
{
    bputs(b, "   fileinto \"");
    filter_sieve_print_quote(b, filter->mailbox);
    bputs(b, "\";"CRLF);
    if (filter->copy)
        bputs(b, "   keep; stop;"CRLF);
    else
        bputs(b, "   stop;"CRLF);
}

static void
filter_sieve_print_discard(struct filter *filter, struct buffer *b)
{
    bputs(b, "\"");
    filter_sieve_print_quote(b, filter->local_part);
    bputc(b, '@');
    filter_sieve_print_quote(b, filter->domain);
    bputs(b, "\" {"CRLF);
    bputs(b, "   discard; stop;"CRLF);
    bputs(b, "}"CRLF""CRLF);
}

/* filter_sieve_print() **************************************************
 *
 * Convert filter into Sieve format string
 *    filter:
 *         b: Target buffer
 ************************************************************************/

BOOL
filter_sieve_print(struct filter *filter, struct buffer *b)
{
    switch (filter->type) {
    case FILTER_SENDER:
        if (!(filter->local_part && filter->domain && filter->mailbox))
            return(NIL);
        if (filter_address_has_wildcard(filter))
            bputs(b, "if envelope :matches \"from\" \"");
        else
            bputs(b, "if envelope :is \"from\" \"");

        filter_sieve_print_quote(b, filter->local_part);
        bputc(b, '@');
        filter_sieve_print_quote(b, filter->domain);
        bputs(b, "\" {"CRLF);
        filter_sieve_print_target(filter, b);
        bputs(b, "}"CRLF""CRLF);
        break;
    case FILTER_RECIPIENT:
        if (!(filter->local_part && filter->domain && filter->mailbox))
            return(NIL);
        if (filter_address_has_wildcard(filter))
            bputs(b, "if address :matches [\"to\", \"cc\"] \"");
        else
            bputs(b, "if address :is [\"to\", \"cc\"] \"");

        filter_sieve_print_quote(b, filter->local_part);
        bputc(b, '@');
        filter_sieve_print_quote(b, filter->domain);
        bputs(b, "\" {"CRLF);
        filter_sieve_print_target(filter, b);
        bputs(b, "}"CRLF""CRLF);
        break;
    case FILTER_SUBJECT:
        if (!(filter->subject && filter->mailbox))
            return(NIL);

        if (strchr(filter->subject, '?') || strchr(filter->subject, '*'))
            bputs(b, "if header :matches \"subject\" \"");
        else
            bprintf(b, "if header :is \"subject\" \"");
        filter_sieve_print_quote(b, filter->subject);
        bputs(b, "\" {"CRLF);
        filter_sieve_print_target(filter, b);
        bputs(b, "}"CRLF""CRLF);
        break;
    case FILTER_BLOCK:
        if (!(filter->local_part && filter->domain))
            return(NIL);

        if (filter_address_has_wildcard(filter))
            bputs(b, "if envelope :matches \"from\" ");
        else
            bputs(b, "if envelope :is \"from\" ");
        filter_sieve_print_discard(filter, b);

        if (filter_address_has_wildcard(filter))
            bputs(b, "if address :all :matches [\"from\", \"sender\"] ");
        else
            bputs(b, "if address :all :is [\"from\", \"sender\"] ");
        filter_sieve_print_discard(filter, b);

        break;
    default:
        return(NIL);
    }
    return(T);
}

/* filter_strip8bit() ***************************************************
 *
 * Useful subset of ISO-8859-1 and UTF-8 is ASCII
 *
 ************************************************************************/

void
filter_strip8bit(struct filter *filter)
{
    string_strip8bit(filter->local_part);
    string_strip8bit(filter->domain);
    string_strip8bit(filter->subject);
    string_strip8bit(filter->mailbox);
}

