#include "fm.h"
#include "myctype.h"
#include <stdio.h>
#include <errno.h>
#include "parsetag.h"
#include "local.h"

static struct mailcap DefaultMailcap[] = {
    {"image/*", DEF_IMAGE_VIEWER " %s", 0, NULL, NULL, NULL},	/* */
    {"audio/basic", DEF_AUDIO_PLAYER " %s", 0, NULL, NULL, NULL},
    {NULL, NULL, 0, NULL, NULL, NULL}
};

struct mailcap *DefaultMailcaps[] = {DefaultMailcap, NULL};
static struct mailcap *loadMailcap(char *filename, int skip_text);

void
initMailcap(struct mailcap ***cap, TextList **cap_list, char *files, TextList *entries, int mailcap_p)
{
    int i, skip_text = 0;
    struct mailcap *mcap;
    TextListItem *tl;

    if (non_null(files))
	*cap_list = make_domain_list(files);
    else
	*cap_list = NULL;

    if (!*cap_list)
	return;

    *cap = New_N(struct mailcap *, (*cap_list)->nitem + 2);
    (*cap)[0] = mcap = New_N(struct mailcap, entries->nitem + 1);

    for (tl = entries->first ; tl ; tl = tl->next, ++mcap)
	if (tl->ptr && tl->ptr[0] && tl->ptr[0] != '#')
	    extractMailcapEntry(tl->ptr, mcap, 0);

    memset(mcap, 0, sizeof(struct mailcap));

    for (i = 1, tl = (*cap_list)->first; tl; i++, tl = tl->next) {
	if (mailcap_p) {
	    skip_text = atoi(tl->ptr);
	    tl = tl->next;
	}

	if (!((*cap)[i] = loadMailcap(tl->ptr, skip_text))) {
	    (*cap)[i] = New(struct mailcap);
	    memset((*cap)[i], 0, sizeof(struct mailcap));
	}
    }

    (*cap)[i] = NULL;
}

int
mailcapMatch(struct mailcap *mcap, char *type)
{
    char *cap = mcap->type, *p;
    int level;
    for (p = cap; *p && *p != '/'; p++) {
	if (tolower(*p) != tolower(*type))
	    return 0;
	type++;
    }
    if ((*p == '/' && *type != '/') ||
	(!*p && *type))
	return 0;
    if (mcap->flags & MAILCAP_CGIOUTPUT)
	level = 2;
    else if (mcap->flags & MAILCAP_HTMLOUTPUT)
	level = 1;
    else
	level = 0;
    if (!*p)
	return 10 + level;
    p++;
    type++;
    if (*p == '*')
	return 10 + level;
    while (*p) {
	if (tolower(*p) != tolower(*type))
	    return 0;
	p++;
	type++;
    }
    if (*type != '\0')
	return 0;
    return 20 + level;
}

struct mailcap *
searchMailcap(struct mailcap *table, char *type, btri_string_tab_t *attr, ParsedURL *pu)
{
    int level = 0;
    struct mailcap *mcap = NULL;
    int i;

    if (table == NULL)
	return NULL;
    for (; table->type; table++) {
	i = mailcapMatch(table, type);
	if (i > level) {
	    if (pu && table->rex) {
		Str url;

		url = parsedURL2Str(pu);

		if (!RegexMatch(table->rex, url->ptr, url->length))
		    continue;
	    }
	    if (table->test) {
		Str command = unquote_mailcap(table->test, type, attr, pu, NULL, TRUE);
		if (!command->length ||
		    !strcasecmp(command->ptr, "false") ||
		    !strcasecmp(command->ptr, "no") ||
		    !strcasecmp(command->ptr, "undef") ||
		    !strcmp(command->ptr, "0") ||
		    (strcasecmp(command->ptr, "true") &&
		     strcasecmp(command->ptr, "yes") &&
		     strcmp(command->ptr, "1") &&
		     system(command->ptr) != 0))
		    continue;
	    }
	    level = i;
	    mcap = table;
	}
    }
    return mcap;
}

static int
matchMailcapAttr(char *p, char *attr, int len, Str *value)
{
    int quoted;
    char *q = NULL;

    if (strncasecmp(p, attr, len) == 0) {
	p += len;
	SKIP_BLANKS(p);
	if (value) {
	    *value = Strnew();
	    if (*p == '=') {
		p++;
		SKIP_BLANKS(p);
		quoted = 0;
		while (*p && (quoted || *p != ';')) {
		    if (quoted || !IS_SPACE(*p))
			q = p;
		    if (quoted)
			quoted = 0;
		    else if (*p == '\\')
			quoted = 1;
		    Strcat_char(*value, *p);
		    p++;
		}
		if (q)
		    Strshrink(*value, p - q - 1);
	    }
	    return 1;
	}
	else {
	    if (*p == '\0' || *p == ';') {
		return 1;
	    }
	}
    }
    return 0;
}

int
extractMailcapEntry(char *mcap_entry, struct mailcap *mcap, int skip_text)
{
    int j, k;
    char *p;
    int quoted;
    Str tmp;

    bzero(mcap, sizeof(struct mailcap));
    p = mcap_entry;
    SKIP_BLANKS(p);
    k = -1;
    for (j = 0; p[j] && p[j] != ';'; j++) {
	if (!IS_SPACE(p[j]))
	    k = j;
    }
    mcap->type = allocStr(p, (k >= 0) ? k + 1 : j);
    if (!p[j] ||
	(skip_text &&
	 (!strncasecmp(mcap->type, "text/", sizeof("text/") - 1) ||
	  !strncasecmp(mcap->type, "message/", sizeof("message/") - 1) ||
	  !strcasecmp(mcap->type, "application/xhtml+xml") ||
	  !strncasecmp(mcap->type, "multipart/", sizeof("multipart/") - 1))))
	return 0;
    p += j + 1;

    SKIP_BLANKS(p);
    k = -1;
    quoted = 0;
    for (j = 0; p[j] && (quoted || p[j] != ';'); j++) {
	if (quoted || !IS_SPACE(p[j]))
	    k = j;
	if (quoted)
	    quoted = 0;
	else if (p[j] == '\\')
	    quoted = 1;
    }
    mcap->viewer = allocStr(p, (k >= 0) ? k + 1 : j);
    p += j;

    while (*p == ';') {
	p++;
	SKIP_BLANKS(p);
	if (matchMailcapAttr(p, "needsterminal", 13, NULL)) {
	    mcap->flags |= MAILCAP_NEEDSTERMINAL;
	}
	else if (matchMailcapAttr(p, "copiousoutput", 13, NULL)) {
	    mcap->flags |= MAILCAP_COPIOUSOUTPUT;
	}
	else if (matchMailcapAttr(p, "x-htmloutput", 12, NULL) ||
		 matchMailcapAttr(p, "htmloutput", 10, NULL)) {
	    mcap->flags |= MAILCAP_HTMLOUTPUT;
	}
	else if (matchMailcapAttr(p, "x-w3m-cgioutput", sizeof("x-w3m-cgioutput") - 1, NULL)) {
	    mcap->flags |= MAILCAP_CGIOUTPUT;
	}
	else if (matchMailcapAttr(p, "x-w3m-internal", sizeof("x-w3m-internal") - 1, NULL)) {
	    mcap->flags |= MAILCAP_INTERNAL;
	}
	else if (matchMailcapAttr(p, "x-w3m-uri", sizeof("x-w3m-uri") - 1, NULL)) {
	    mcap->flags |= MAILCAP_URI;
	}
	else if (matchMailcapAttr(p, "x-w3m-match", sizeof("x-w3m-match") - 1, &tmp)) {
	    mcap->rex = newRegex(tmp->ptr, FALSE, NULL, NULL);
	}
	else if (matchMailcapAttr(p, "x-w3m-nc-match", sizeof("x-w3m-nc-match") - 1, &tmp)) {
	    mcap->rex = newRegex(tmp->ptr, TRUE, NULL, NULL);
	}
	else if (matchMailcapAttr(p, "test", 4, &tmp)) {
	    mcap->test = allocStr(tmp->ptr, tmp->length);
	}
	else if (matchMailcapAttr(p, "nametemplate", 12, &tmp)) {
	    mcap->nametemplate = allocStr(tmp->ptr, tmp->length);
	}
	else if (matchMailcapAttr(p, "edit", 4, &tmp)) {
	    mcap->edit = allocStr(tmp->ptr, tmp->length);
	}
	quoted = 0;
	while (*p && (quoted || *p != ';')) {
	    if (quoted)
		quoted = 0;
	    else if (*p == '\\')
		quoted = 1;
	    p++;
	}
    }
    return 1;
}

static struct mailcap *
loadMailcap(char *filename, int skip_text)
{
    FILE *f;
    int i, n;
    Str tmp;
    struct mailcap *mcap;

    f = fopen(expandName(filename), "r");
    if (f == NULL)
	return NULL;
    i = 0;
    while (tmp = Strfgets(f), tmp->length > 0) {
	if (tmp->ptr[0] != '#')
	    i++;
    }
    fseek(f, 0, 0);
    n = i;
    mcap = New_N(struct mailcap, n + 1);
    i = 0;
    while (tmp = Strfgets(f), tmp->length > 0) {
	if (tmp->ptr[0] == '#')
	    continue;
    redo:
	while (IS_SPACE(Strlastchar(tmp)))
	    Strshrink(tmp, 1);
	if (Strlastchar(tmp) == '\\') {
	    /* continuation */
	    Strshrink(tmp, 1);
	    Strcat(tmp, Strfgets(f));
	    goto redo;
	}
	if (extractMailcapEntry(tmp->ptr, &mcap[i], skip_text))
	    i++;
    }
    bzero(&mcap[i], sizeof(struct mailcap));
    fclose(f);
    return mcap;
}

char *
acceptableMimeTypes(void)
{
    static Str types = NULL;
    TextList *l;
    btri_string_tab_t *mhash;
    char *p;
    struct mailcap *mp, **pmp;
    void *dummy = NULL;

    struct {
	char *name;
	int len;
    } builtins[] = {
	{"text", sizeof("text") - 1},
	{"message", sizeof("message") - 1},
	{"multipart", sizeof("multipart") - 1},
	{"application", sizeof("application") - 1},
	{"image", sizeof("image") - 1},
	{NULL, 0},
    }, *bp;

    if (types != NULL)
	return types->ptr;

    /* generate acceptable media types */
    l = newTextList();
    mhash = btri_new_node(&btri_string_tab_desc);

    for (bp = builtins ; bp->name ; ++bp) {
	pushText(l, bp->name);
	btri_search_mem(&btri_string_tab_desc, BTRI_OP_ADD | BTRI_OP_WR,
			bp->name, bp->len, mhash, &dummy);
    }

    for (pmp = UserMailcap ; (mp = *pmp) ; ++pmp) {
	Str mt;

	for (; mp->type; mp++) {
	    if (!(p = strchr(mp->type, '/')))
		continue;

	    if (btri_fast_search_mem(mp->type, p - mp->type, mhash, &dummy) == bt_failure) {
		mt = Strnew_charp_n(mp->type, p - mp->type);
		pushText(l, mt->ptr);
		btri_search_mem(&btri_string_tab_desc, BTRI_OP_ADD | BTRI_OP_WR,
				mt->ptr, mt->length, mhash, &dummy);
	    }
	}
    }

    while ((p = popText(l))) {
	if (!types)
	    types = Strnew();
	else
	    Strcat_charp(types, ", ");

	Strcat_charp(types, p);
	Strcat_charp(types, "/*");
    }

    return types->ptr;
}

struct mailcap *
searchExtViewer(char *type, btri_string_tab_t *attr, ParsedURL *pu, struct mailcap **cap)
{
    if (cap) {
	struct mailcap *p;

	for (; *cap ; ++cap)
	    if ((p = searchMailcap(*cap, type, attr, pu)))
		return p;
    }

    return NULL;
}

#define MC_NORMAL 0
#define MC_PREC   1
#define MC_PREC2  2
#define MC_QUOTED 3

#define MCF_SQUOTED (1 << 0)
#define MCF_DQUOTED (1 << 1)

Str
quote_mailcap(char *s, int flag)
{
    Str d;

    d = Strnew();

    for (;; ++s)
	switch (*s) {
	case '\0':
	    goto end;
	case '$':
	case '`':
	case '"':
	case '\\':
	    if (!(flag & MCF_SQUOTED))
		Strcat_char(d, '\\');

	    Strcat_char(d, *s);
	    break;
	case '\'':
	    if (flag & MCF_SQUOTED) {
		Strcat_charp(d, "'\\''");
		break;
	    }
	default:
	    if (!flag && !IS_ALNUM(*s))
		Strcat_char(d, '\\');
	case '_':
	case '.':
	case ':':
	case '/':
	    Strcat_char(d, *s);
	    break;
	}
end:
    return d;
}

static Str
unquote_mailcap_loop(char *qstr, char *type, btri_string_tab_t *attr, ParsedURL *pu,
		     int *mc_stat, int flag0, int need_shell_quote)
{
    Str str, tmp, test, then;
    char *p, *q;
    int status = MC_NORMAL, prev_status = MC_NORMAL, sp = 0, flag;

    if (mc_stat)
	*mc_stat = 0;
    
    if (qstr == NULL)
	return NULL;

    str = Strnew();
    tmp = test = then = NULL;

    for (flag = flag0, p = qstr; *p; p++) {
	if (status == MC_QUOTED) {
	    if (prev_status == MC_PREC2)
		Strcat_char(tmp, *p);
	    else
		Strcat_char(str, *p);
	    status = prev_status;
	    continue;
	}
	else if (*p == '\\') {
	    prev_status = status;
	    status = MC_QUOTED;
	    continue;
	}
	switch (status) {
	case MC_NORMAL:
	    if (*p == '%') {
		status = MC_PREC;
	    }
	    else {
		if (*p == '\'') {
		    if (!flag0 && flag & MCF_SQUOTED)
			flag &= ~MCF_SQUOTED;
		    else if (!flag)
			flag |= MCF_SQUOTED;
		}
		else if (*p == '"') {
		    if (!flag0 && flag & MCF_DQUOTED)
			flag &= ~MCF_DQUOTED;
		    else if (!flag)
			flag |= MCF_DQUOTED;
		}

		Strcat_char(str, *p);
	    }

	    break;
	case MC_PREC:
	    if (IS_ALPHA(*p)) {
		switch (*p) {
		case 's':
		    if (pu && pu->file) {
			Strcat_charp(str, need_shell_quote ? quote_mailcap(pu->file, flag)->ptr : pu->file);
			if (mc_stat)
			    *mc_stat |= MCSTAT_REPNAME;
		    }
		    break;
		case 't':
		    if (type) {
			Strcat_charp(str, need_shell_quote ? quote_mailcap(type, flag)->ptr : type);
			if (mc_stat)
			    *mc_stat |= MCSTAT_REPTYPE;
		    }
		    break;
		case 'h':
		    if (pu && pu->host) {
			Strcat_charp(str, need_shell_quote ? quote_mailcap(pu->host, flag)->ptr : pu->host);
			if (mc_stat)
			    *mc_stat |= MCSTAT_REPHOST;
		    }
		    break;
		case 'p':
		    if (pu && pu->port) {
			Strcat(str, Sprintf("%d", pu->port));
			if (mc_stat)
			    *mc_stat |= MCSTAT_REPPORT;
		    }
		    break;
		case 'u':
		    if (pu) {
			Strcat(str, parsedURL2Str(pu));
			if (mc_stat)
			    *mc_stat |= MCSTAT_REPURL;
		    }
		    break;
		}
		status = MC_NORMAL;
	    }
	    else if (*p == '?') {
		if (attr && btri_fast_search_mem("?", sizeof("?") - 1, attr, (void **)&q) != bt_failure) {
		    Strcat_charp(str, q);
		    if (mc_stat)
			*mc_stat |= MCSTAT_REPQSTR;
		}
		status = MC_NORMAL;
	    }
	    else if (*p == '{') {
		status = MC_PREC2;
		test = then = NULL;
		tmp = Strnew();
	    }
	    else if (*p == '%') {
		Strcat_char(str, *p);
	    }
	    break;
	case MC_PREC2:
	    if (sp > 0 || *p == '{') {
		Strcat_char(tmp, *p);

		switch (*p) {
		case '{':
		    ++sp;
		    break;
		case '}':
		    --sp;
		    break;
		default:
		    break;
		}
	    }
	    else if (*p == '}') {
		if (test) {
		    int substat = 0;
		    Str exp;

		    if ((exp = unquote_mailcap_loop(test->ptr, type, attr, pu, &substat, flag, need_shell_quote)) && substat) {
			if (!then)
			    then = tmp;

			if (!then->length)
			    Strcat(str, exp);
			else {
			    substat = 0;
			    Strcat(str, unquote_mailcap_loop(then->ptr, type, attr, pu, &substat, flag, need_shell_quote));
			}
		    }
		    else if (then) {
			substat = 0;
			Strcat(str, unquote_mailcap_loop(tmp->ptr, type, attr, pu, &substat, flag, need_shell_quote));
		    }

		    if (mc_stat)
			*mc_stat |= substat;
		}
		else if (attr && tmp->length &&
			 (Strlower(tmp),
			  btri_fast_search_mem(tmp->ptr, tmp->length, attr, (void **)&q) != bt_failure)) {
		    Strcat_charp(str, need_shell_quote ? quote_mailcap(q, flag)->ptr : q);
		    if (mc_stat)
			*mc_stat |= MCSTAT_REPPARAM;
		}
		status = MC_NORMAL;
	    }
	    else if (tmp->length && !test && *p == '?') {
		test = Strnew_m_charp("%", tmp->ptr, NULL);
		Strclear(tmp);
	    }
	    else if (test && !then && *p == ':') {
		then = tmp;
		tmp = Strnew();
	    }
	    else {
		Strcat_char(tmp, *p);
 	    }
	    break;
	}
    }
    return str;
}

Str
unquote_mailcap(char *qstr, char *type, btri_string_tab_t *attr, ParsedURL *pu, int *mc_stat, int need_shell_quote)
{
    return unquote_mailcap_loop(qstr, type, attr, pu, mc_stat, 0, need_shell_quote);
}

/* Local Variables:    */
/* c-basic-offset: 4   */
/* tab-width: 8        */
/* End:                */
