static char rcsid[] = "@(#)$Id: hdrdecode.c,v 1.22 2006/07/01 07:37:48 hurtta Exp $";

/******************************************************************************
 *  The Elm (ME+) Mail System  -  $Revision: 1.22 $   $State: Exp $
 *
 *  Author: Kari Hurtta <hurtta+elm@posti.FMI.FI> (was hurtta+elm@ozone.FMI.FI)
 *
 *  Partially based on mime_decode.c, which is initially 
 *     written by: Michael Elkins <elkins@aero.org>, 1995
 *****************************************************************************/

#include "headers.h"
#include "s_me.h"

DEBUG_VAR(Debug,__FILE__,"mime");

int index_hex[128] = {
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
     0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,
    -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
};

int index_64[128] = {
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
    52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
    -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
    15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
    -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
    41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
};

char * blstrpbrk (string, set) 
     char *string;
     CONST char *set;
{
    char *p;

    for (p = string; *p; p++) 
	if ('\\' == *p) {
	    p++;
	    if ('\0' == *p)
		return NULL;
	} else if (NULL != strchr(set,*p))
	    return p;
    return NULL;
}

void append_string(res,s)
     struct string ** res; 
     CONST struct string *s;
{
    if (!*res)
	*res = dup_string(s);
    else {
	struct string *t = cat_strings(*res,s,1);
	free_string(res);
	*res = t;
    }
}

static unsigned char * us_str P_((char *str));
static unsigned char * us_str(str)
     char *str;
{
    return (unsigned char *)str;
}

static char *us2s P_((unsigned char *str));
static char *us2s(str) 
     unsigned char *str;
{
    return (char *)str;
}

static struct string * hdr_qp_decode P_((charset_t set,char *buffer,
					 char *lang));
static struct string * hdr_qp_decode(set,buffer,lang)
     charset_t set;
     char *buffer;
     char *lang;
{
    struct string *ret = lang ? new_langstring(set,lang) : new_string(set);
    unsigned char *p;

    for (p = us_str(buffer); *p; p++) {
	switch(*p) {
	    int a,b;
	case '_':
	    add_streambyte_to_string(ret,' ');
	    break;
	case '=':
	    p++;
	    if (!*p)
		goto fail;
	    if ((a = hex(*p)) < 0)
		goto fail;
	    p++;
	    if (!*p)
		goto fail;
	    if ((b = hex(*p)) < 0)
		goto fail;
	    add_streambyte_to_string(ret,a*16+b);
	    break;
	default:
	    add_streambyte_to_string(ret,*p);
	    break;
	}
    }

    return ret;
 fail:
    DPRINT(Debug,20,(&Debug, 
		     "hdr_qp_decode: FAIL: buffer=%s, next char=%c\n",
		     buffer,*p));
    free_string(&ret);
    return NULL;
}

static struct string * hdr_base64_decode P_((charset_t set,char *buffer,
					     char *lang));
static struct string * hdr_base64_decode(set,buffer,lang)
     charset_t set;
     char *buffer;
     char *lang;
{
    struct string *ret = lang ? new_langstring(set,lang) : new_string(set);
    int val = 0;
    int bits = 0;
    unsigned char *p;

    for (p = us_str(buffer); *p; p++) {
	int a;

	if ('=' == *p)
	    break;
	if ((a=base64(*p)) < 0)
	    goto fail;

	val = (val << 6) | a;
	bits += 6;
	    
	if (bits >= 8) {
	    int n;

	    n     = val >> (bits-8);
	    val  -= n << (bits-8);
	    bits -= 8;

	    add_streambyte_to_string(ret,n);
	}
    }

    return ret;
 fail:
    DPRINT(Debug,20,(&Debug, 
		     "hdr_base64_decode: FAIL: buffer=%s, next char=%c\n",
		     buffer,*p));
    free_string(&ret);
    return NULL;

}

static struct string * hdr_decode_word P_((char *buffer));
static struct string * hdr_decode_word(buffer)
     char *buffer;
{
    char * temp = safe_strdup(buffer);
    char *p = temp;
    char *sn = NULL;
    char *lang = NULL;
    char E = '\0';
    char *encoded = NULL;
    struct string *ret = NULL;
    charset_t set;
	char *front, *end;
	struct string *fstr, *estr;

	/* Pasear: front, end are used to solve buffer: abc""=?...?=" problem */
	front = p;
	while (*p && '=' != *p) ++p;
	if (front != p && '=' == *p && '"' == *(p-1)) *(p-1) = '\0';
    if ('=' != *p)
	goto fail;
	*p = '\0'; ++p;
    if ('?' != *p++)
	goto fail;
    sn = p;
    while (*p && '?' != *p && '*' != *p)
	p++;
    if (!*p)
	goto fail;
    if ('*' == *p) {
	*p++ = '\0';
	lang = p;

	while (*p && '?' != *p && '*' != *p)
	    p++;
	if ('?' != *p)
	    goto fail;	
    } 

    *p++ = '\0';
    E = *p++;
    if ('Q' != E && 'B' != E &&
	'q' != E && 'b' != E)
	goto fail;
    if ('?' != *p++)
	goto fail;
    encoded = p;
    while (*p && '?' != *p)
	p++;
    if (!*p)
	goto fail;
    *p = '\0';
    p++;
    if ('=' != *p++)
	goto fail;
	if ('"' == *p) ++p;
	end = p;

    set = MIME_name_to_charset(sn,CHARSET_create);

    switch(E) {
    case 'Q':
    case 'q':
	ret = hdr_qp_decode(set,encoded,lang);
	break;
    case 'B':
    case 'b':
	ret = hdr_base64_decode(set,encoded,lang);
	break;
    }

	/* Pasear */
	if (ret){
		estr = ret;
		fstr = new_string2(system_charset,us_str(front));
		fstr = ret = cat_strings(fstr, ret, 0);
		free_string(&estr);
		estr = new_string2(system_charset,us_str(end));
		ret = cat_strings(ret, estr, 0);
		free_string(&estr);
		free_string(&fstr);
	}

 fail:
    if (!ret) {
	DPRINT(Debug,20,(&Debug, 
			 "hdr_decode_word: FAIL: buffer='%s' -- next char=%c\n",
			 buffer,*p));
    }
    free(temp);
    return ret;
}


static struct string * hdr_dequote P_((char *buffer,
				       charset_t defcharset));
static struct string * hdr_dequote(buffer,defcharset)
     char *buffer;
     charset_t defcharset;
{
    struct string * ret;
    char *p;
    char *t = buffer;

    for (p = buffer; *p; p++) { 
	if ('\\' == *p) {
	    p++;
	    if ('\0' == *p)
		break;
	} else if ('"' == *p)
	    continue;
	*t++ = *p;
    }
    *t = '\0';

    ret = new_string2(defcharset,us_str(buffer));
    return ret;
}

static struct string * hdr_comment P_((char *buffer,
				       charset_t defcharset,
				       int demime));
static struct string * hdr_comment(buffer,defcharset,demime)
     char *buffer;
     charset_t defcharset;
     int demime;
{
    struct string * ret = new_string(defcharset);

    char * walk, *ptr;
    unsigned char last_char = 0;
    
    for (ptr = buffer; ptr && *ptr; ptr = walk) {
	unsigned char safe = 0;
	struct string * ok = NULL;
	int nostore = 0;

	walk = blstrpbrk(ptr," \t\r\n()");
	if (walk) {
	    safe = *walk;
	    *walk = '\0';
	    walk++;
	}

	/* Try decode it (perhaps not encoded word at all) */
	ok = hdr_decode_word(ptr);

	if (!ok) {
	    /* now if it was not encoded, then we need add last space */
	    if (last_char) {
		unsigned char ascii[2];
		ascii[0] = last_char;
		ascii[1] = '\0';
		add_ascii_to_string(ret,ascii);
	    }

	    /* If decoding failed add word as is */
	    ok = new_string2(defcharset,us_str(ptr));
	    nostore = 1;
	} else if (last_char == ')' || last_char == '(') {

	    /* Also we need add last char if not space */
	    add_streambyte_to_string(ret,last_char);
	}

	/* Now compine strings */
	append_string(&ret,ok);
	free_string(&ok);

	if (nostore) {
	    /* If last was not encoded we are not going to delete
	       space between words
	    */
	    if (safe) {
		unsigned char ascii[2];
		ascii[0] = safe;
		ascii[1] = '\0';
		add_ascii_to_string(ret,ascii);
	    }
	    last_char = 0;
	} else
	    last_char = safe;

    }

    return ret;
}

static struct string * hdr_phrase P_((char *buffer,
				      charset_t defcharset,
				      int demime));
static struct string * hdr_phrase(buffer,defcharset,demime)
     char *buffer;
     charset_t defcharset;
     int demime;
{
    struct string * ret = new_string(defcharset);
    char **tokenized = rfc822_tokenize(buffer);
    unsigned char * last_char = NULL;
    int i, encoded;
	char* p;

    for (i = 0; tokenized[i]; i++) {

	struct string * ok = NULL;
	int nostore = 0;

	/* Pasear: detect if it is a encoded string */
	encoded = 0;
	if ('"' == tokenized[i][0]){
		p = tokenized[i];
		while (*p && *p != '=') ++p;
		if (*p && *p == '=' && *(p+1) && *(p+1) == '?' )
			encoded = 1;
	}


	if ('(' == tokenized[i][0]) {
	    /* we need add last space */
	    if (last_char) 
		add_ascii_to_string(ret,last_char);
	    ok = hdr_comment(tokenized[i],defcharset,demime);
	    nostore = 1;
	} else if (!encoded && '"' == tokenized[i][0]) {
	    /* we need add last space */
	    if (last_char) 
		add_ascii_to_string(ret,last_char);

	    ok = hdr_dequote(tokenized[i],defcharset);
	    nostore = 1;
	} else {
	    /* Try decode it (perhaps not encoded word at all) */
	    if (demime)
		ok = hdr_decode_word(tokenized[i]);

	    if (!ok) {
		/* now if it was not encoded, then we need add last space */
		if (last_char) 
		    add_ascii_to_string(ret,last_char);

		/* If decoding failed add word as is */
		ok = new_string2(defcharset,us_str(tokenized[i]));
		nostore = 1;
	    }
	}

	/* Now compine strings */
	append_string(&ret,ok);
	free_string(&ok);
	
	last_char = 0;
	if (!nostore && tokenized[i+1] &&
	    (' ' == tokenized[i+1][0] ||
	     '\t' == tokenized[i+1][0] ||
	     '\r' == tokenized[i+1][0] ||
	     '\n' == tokenized[i+1][0])) {
	    i++;
	    last_char = us_str(tokenized[i]);
	}
    }

    free_rfc822tokenized(tokenized);
    return ret;
}


static struct string * hdr_text P_((char *buffer,
				    charset_t defcharset,
				    int demime));
static struct string * hdr_text(buffer,defcharset,demime)
     char *buffer;
     charset_t defcharset;
     int demime;
{
    struct string * ret = NULL;

    char * walk, *ptr;
    unsigned char * last_space = NULL;
    
    for (ptr = buffer; ptr && *ptr; ptr = walk) {
	unsigned char safe = 0;
	struct string * ok = NULL;
	int nostore = 0;
	char * skipstore = NULL;

	walk = strpbrk(ptr," \t\r\n");
	if (walk) {
	    safe = *walk;
	    *walk = '\0';
	    walk++;

	    skipstore = walk;

	    /* Jump to next word */
	    while (*walk &&
		   NULL != strchr(" \t\r\n", *walk))
		walk++;

	}

	/* Try decode it (perhaps not encoded word at all) */
	if (demime)
	    ok = hdr_decode_word(ptr);

	if (!ok) {
	    /* now if it was not encoded, then we need add last space */
	    if (last_space) {

		if (!ret)
		    ret = new_string(defcharset);
		add_ascii_to_string(ret,last_space);
	    }

	    /* If decoding failed add word as is */
	    ok = new_string2(defcharset,us_str(ptr));
	    nostore = 1;
	}

	/* Now compine strings */
	append_string(&ret,ok);
	free_string(&ok);

	if (last_space)
	    free(last_space);
	last_space = NULL;

	if (safe) {
	    int L;
	    if (!skipstore) 
		panic("STRING PANIC",__FILE__,__LINE__,"hdr_text",
		      "skipstore not set",0);

	    L = walk - skipstore;
	    if (L < 0)
		panic("STRING PANIC",__FILE__,__LINE__,"hdr_text",
		      "bad len",0);

	    last_space = safe_malloc(L+2);
	    last_space[0] = safe;

	    if (L > 0)
		strncpy(us2s(last_space)+1,skipstore,L);
	    last_space[L+1] = '\0';
	}


	if (nostore) {
	    /* If last was not encoded we are not going to delete
	       space between words
	    */
	    if (last_space) {
		if (!ret)
		    ret = new_string(defcharset);
		add_ascii_to_string(ret,last_space);

		free(last_space);
		last_space = NULL;
	    }

	} 
    }

    return ret;
}


/* class is one of HDR_PHRASE, HDR_COMMENT, HDR_TEXT */

struct string * hdr_to_string(class,buffer,defcharset, demime)
     int class;
     CONST char *buffer;
     charset_t defcharset;
     int demime;
{
    struct string * ret = NULL;
    char *temp = safe_strdup(buffer);

    switch(class) {
    case HDR_PHRASE:
	ret = hdr_phrase(temp,defcharset,demime);
	break;
    case HDR_COMMENT:
	ret = hdr_comment(temp,defcharset,demime);
	break;
    case HDR_TEXT:
	ret = hdr_text(temp,defcharset,demime);
	break;
    }

    free(temp);

    if (!ret) {
	ret = new_string(defcharset);
	DPRINT(Debug,30,(&Debug, 
			 "hdr_to_string: Returning empty header...\n"));
    }

    DPRINT(Debug,30,(&Debug, 
		     "hdr_to_string=%p  (class=%d, buffer='%s', defcharset=%p '%s', demime=%d)\n",
		     ret,
		     class,buffer,
		     defcharset,
		     defcharset->MIME_name ? defcharset->MIME_name : "<none>",
		     demime));    
    
    return ret;
}

/*
 * Local Variables:
 *  mode:c
 *  c-basic-offset:4
 *  buffer-file-coding-system: iso-8859-1
 * End:
 */


syntax highlighted by Code2HTML, v. 0.9.1