/*			MIME Message Parse			HTMIME.c
**			==================
**
**	This is RFC 1341-specific code.
**	The input stream pushed into this parser is assumed to be
**	stripped on CRs, ie lines end with LF, not CR LF.
**	(It is easy to change this except for the body part where
**	conversion can be slow.)
**
** History:
**	   Feb 92	Written Tim Berners-Lee, CERN
**
*/
#include "HTUtils.h"
#include "HTMIME.h"		/* Implemented here */
#include "HTAlert.h"

#include "LYLeaks.h"

#define FREE(x) if (x) {free(x); x = NULL;}


/*		MIME Object
**		-----------
*/

PUBLIC char * redirecting_url=NULL;
extern int loading_length;	/* from HTFormat.c (for HTCopy) */
extern BOOL HTJapanese;		/* from SGML.c */

typedef enum _MIME_state {
	MIME_TRANSPARENT,	/* put straight through to target ASAP! */
	BEGINNING_OF_LINE,
	CONTENT_,
	CONTENT_ENCODING,
	CONTENT_LENGTH,
	CONTENT_T,
	CONTENT_TRANSFER_ENCODING,
	CONTENT_TYPE,
	SKIP_GET_VALUE,		/* Skip space then get value */
	GET_VALUE,		/* Get value till white space */
	JUNK_LINE,		/* Ignore the rest of this folded line */
	NEWLINE,		/* Just found a LF .. maybe continuation */
	CHECK,			/* check against check_pointer */
	MIME_NET_ASCII,		/* Translate from net ascii */
	MIME_IGNORE,		/* ignore entire file */
	LOCATION		/* different URL */
	/* TRANSPARENT and IGNORE are defined as stg else in _WINDOWS */
} MIME_state;

#define VALUE_SIZE 1024		/* @@@@@@@ Arbitrary? */
struct _HTStream {
	CONST HTStreamClass *	isa;
	
	BOOL			net_ascii;	/* Is input net ascii? */
	MIME_state		state;		/* current state */
	MIME_state		if_ok;		/* got this state if match */
	MIME_state		field;		/* remember which field */
	MIME_state		fold_state;	/* state on a fold */
	CONST char *		check_pointer;	/* checking input */
	
	char *			value_pointer;	/* storing values */
	char 			value[VALUE_SIZE];
	
	HTParentAnchor *	anchor;		/* Given on creation */
	HTStream *		sink;		/* Given on creation */
	
	char *			boundary;	/* For multipart */
	
	HTFormat		encoding;	/* Content-Transfer-Encoding */
	char *			compression_encoding;
	int			content_length;
	HTFormat		format;		/* Content-Type */
	HTStream *		target;		/* While writing out */
	HTStreamClass		targetClass;
	
	HTAtom *		targetRep;	/* Converting into? */
};


/*_________________________________________________________________________
**
**			A C T I O N 	R O U T I N E S
*/

/*	Character handling
**	------------------
**
**	This is a FSM parser which is tolerant as it can be of all
**	syntax errors.  It ignores field names it does not understand,
**	and resynchronises on line beginnings.
*/

PRIVATE void HTMIME_put_character ARGS2(HTStream *, me, char, c)
{
    if (me->state == MIME_TRANSPARENT) {
    	(*me->targetClass.put_character)(me->target, c);/* MUST BE FAST */
	return;
    }
    
    /* This slightly simple conversion just strips CR and turns LF to
    ** newline. On unix LF is \n but on Mac \n is CR for example.
    ** See NetToText for an implementation which preserves single CR or LF.
    */
    if (me->net_ascii) {
        c = FROMASCII(c);
	if (c == CR)
	    return;
	else if (c == LF)
	    c = '\n';
    }
    
    switch(me->state) {

    case MIME_IGNORE:
    	return;

    case MIME_TRANSPARENT:		/* Not reached see above */
    	(*me->targetClass.put_character)(me->target, c);
	return;
	
    case MIME_NET_ASCII:
    	(*me->targetClass.put_character)(me->target, c); /* MUST BE FAST */
	return;

    case NEWLINE:
	if (c != '\n' && WHITE(c)) {		/* Folded line */
	    me->state = me->fold_state;	/* pop state before newline */
	    break;
	}
	
	/*	else Falls through */
	
    case BEGINNING_OF_LINE:
        me->net_ascii = YES;
        switch(c) {
	case 'c':
	case 'C':
	    me->check_pointer = "ontent-";
	    me->if_ok = CONTENT_;
	    me->state = CHECK;
	    break;
	case 'l':
        case 'L':
          me->check_pointer = "ocation:";
          me->if_ok = LOCATION;
          me->state = CHECK;
          if (TRACE)
            fprintf (stderr,
                     "[MIME] Got L at beginning of line\n");
          break;

	case '\n':			/* Blank line: End of Header! */
	    {
	        me->net_ascii = NO;
		if (strchr(HTAtom_name(me->format), ';') != NULL) {
		    char *cp=NULL, *cp1, *cp2;
		    extern int current_char_set;
		    extern char *LYchar_set_names[];
		    extern BOOLEAN HTUseRawLatin;

		    if (TRACE)
		        fprintf(stderr,
				"HTMIME: Extended MIME Content-Type is %s\n",
				HTAtom_name(me->format));
		    /*
		     * Note that the Content-Type value was converted
		     * to lower case when loaded into me->format. - FM
		     */
		    StrAllocCopy(cp, HTAtom_name(me->format));
		    if ((cp1=strchr(cp, ';')) != NULL) {
		        if ((cp2=strstr(cp1, "charset")) != NULL) {
			    cp2 += 7;
			    while (*cp2 == ' ' || *cp2 == '=')
			        cp2++;
			    if (!strncmp(cp2, "us-ascii", 8) ||
			        !strncmp(cp2, "iso-8859-1", 10)) {
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "iso-8859-1");
			    } else if
			       (!strncmp(cp2, "iso-8859-2", 10) &&
				!strncmp(LYchar_set_names[current_char_set],
				 	 "ISO Latin 2", 11)) {
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "iso-8859-2");
				HTUseRawLatin = TRUE;
			    } else if
			       (!strncmp(cp2, "koi8-r", 6) &&
				!strncmp(LYchar_set_names[current_char_set],
					 "KOI8-R character set", 20)) {
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "koi8-r");
				HTUseRawLatin = TRUE;
			    } else if
			       (HTJapanese &&
			        !strncmp(cp2, "iso-2022-jp", 11)) {
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "iso-2022-jp");
			    } else if
			       (HTJapanese &&
			        !strncmp(cp2, "iso-2022-jp-2", 13)) {
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "iso-2022-jp-2");
			    } else if
			       (!strncmp(cp2, "euc-kr", 6)) {
			       /* Let this through as is, for now. */
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "euc-kr");
				HTUseRawLatin = TRUE;
				HTAlert(me->anchor->charset);
			    } else if
			       (!strncmp(cp2, "iso-2022-kr", 11)) {
			       /* Let this through as is, for now. */
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "iso-2022-kr");
				HTUseRawLatin = TRUE;
				HTAlert(me->anchor->charset);
			    } else if
			       (!strncmp(cp2, "iso-8859-", 9)) {
			       /* Let these through as is, for now. */
				*cp1 = '\0';
				me->format = HTAtom_for(cp);
				StrAllocCopy(me->anchor->charset,
					     "iso-8859-");
				StrAllocCat(me->anchor->charset, " ");
				me->anchor->charset[9] = cp2[9];
				HTUseRawLatin = TRUE;
				HTAlert(me->anchor->charset);
			    }
			}
		    }
		    FREE(cp);
		}
		if (!me->compression_encoding) {
	            if (TRACE) {
		        fprintf(stderr,
		    "HTMIME: MIME Content-Type is '%s', converting to '%s'\n",
			 HTAtom_name(me->format), HTAtom_name(me->targetRep));
		    }
		} else {
	            if (TRACE) {
		        fprintf(stderr,
	     "HTMIME: MIME Content-Type is '%s',\n", HTAtom_name(me->format));
		    }
		    me->format = HTAtom_for(me->compression_encoding);
	            if (TRACE) {
		        fprintf(stderr,
	     "        Treating as '%s'.  Converting to '%s'\n",
			 HTAtom_name(me->format), HTAtom_name(me->targetRep));
		    }
		}
		me->target = HTStreamStack(me->format, me->targetRep,
	 				   me->sink , me->anchor);
		if (!me->target) {
		    if (TRACE)
		        fprintf(stderr, "HTMIME: Can't translate! ** \n");
		    me->target = me->sink;	/* Cheat */
		}
		if (me->target) {
		    me->targetClass = *me->target->isa;
		/* Check for encoding and select state from there @@ */
		
		    me->state = MIME_TRANSPARENT; /* From now push straigh through */
		} else {
		    me->state = MIME_IGNORE;		/* What else to do? */
		}
		FREE(me->compression_encoding);
	    }
	    break;
	    
	default:
	   goto bad_field_name;
	   break;
	   
	} /* switch on character */
        break;
	
    case CHECK:				/* Check against string */
        if (TOLOWER(c) == *(me->check_pointer)++) {
	    if (!*me->check_pointer)
	        me->state = me->if_ok;
	} else {		/* Error */
	    if (TRACE)
	        fprintf(stderr,
	    	    "HTMIME: Bad character `%c' found where `%s' expected\n",
		c, me->check_pointer - 1);
	    goto bad_field_name;
	}
	break;
	
    case CONTENT_:
	if (TRACE)
           fprintf (stderr,
                 "[MIME] in case CONTENT_\n");
        switch(c) {
        case 't':
        case 'T':
          me->state = CONTENT_T;
          if (TRACE)
            fprintf (stderr,
                     "[MIME] Was CONTENT_, found T, state now CONTENT_T\n");
          break;

        case 'e':
        case 'E':
          me->check_pointer = "ncoding:";
          me->if_ok = CONTENT_ENCODING;
          me->state = CHECK;
          if (TRACE)
            fprintf (stderr,
                     "[MIME] Was CONTENT_, found E, checking for 'ncoding:'\n");
          break;

        case 'l':
        case 'L':
          me->check_pointer = "ength:";
          me->if_ok = CONTENT_LENGTH;
          me->state = CHECK;
          if (TRACE)
            fprintf (stderr,
                     "[MIME] Was CONTENT_, found L, checking for 'ength:'\n");
          break;

        default:
          if (TRACE)
            fprintf (stderr,
                     "[MIME] Was CONTENT_, found nothing; bleah\n");
          goto bad_field_name;

        } /* switch on character */
      break;

    case CONTENT_T:
      if (TRACE)
        fprintf (stderr,
                 "[MIME] in case CONTENT_T\n");
      switch(c) {
	case 'r':
	case 'R':
	    me->check_pointer = "ansfer-encoding:";
	    me->if_ok = CONTENT_TRANSFER_ENCODING;
	    me->state = CHECK;
	    break;
	    
	case 'y':
	case 'Y':
	    me->check_pointer = "pe:";
	    me->if_ok = CONTENT_TYPE;
	    me->state = CHECK;
	    break;
	    
	default:
	    goto bad_field_name;
	    
	} /* switch on character */
	break;
	
    case CONTENT_TYPE:
    case CONTENT_TRANSFER_ENCODING:
    case CONTENT_ENCODING:
    case CONTENT_LENGTH:
    case LOCATION:
        me->field = me->state;		/* remember it */
	me->state = SKIP_GET_VALUE;
				/* Fall through! */
    case SKIP_GET_VALUE:
    	if (c == '\n') {
	   me->fold_state = me->state;
	   me->state = NEWLINE;
	   break;
	}
	if (WHITE(c))
	    break;	/* Skip white space */
	
	me->value_pointer = me->value;
	me->state = GET_VALUE;   
	/* Fall through to store first character */
	
    case GET_VALUE:
    	if (WHITE(c)) {			/* End of field */
	    *me->value_pointer = 0;
	    switch (me->field) {
	    case CONTENT_TYPE:
                if (TRACE)
                  fprintf (stderr,
                       "[MIME_put_char] Picked up Content-Type: '%s'\n",
                        ((me->value && *me->value) ? me->value : ""));
	        {
		  /*
		   * Force the Content-type value to all lower case. FM
		   */
		  char *cp;
		  for(cp=(char *)me->value; *cp; cp++)
		      *cp = TOLOWER(*cp);
		}
	        me->format = HTAtom_for(me->value);
		break;
	    case CONTENT_TRANSFER_ENCODING:
                if (TRACE)
                  fprintf (stderr,
                "[MIME_put_char] Picked up Content-Transfer-Encoding: '%s'\n",
                        ((me->value && *me->value) ? me->value : ""));
	        me->encoding = HTAtom_for(me->value);
		break;
            case CONTENT_ENCODING:
                if (TRACE)
                  fprintf(stderr,
                       "[MIME_put_char] Picked up Content-Encoding: '%s'\n",
                        ((me->value && *me->value) ? me->value : ""));
		if (!(me->value && *me->value))
		    break;
		StrAllocCopy(me->anchor->content_encoding, me->value);
		FREE(me->compression_encoding);
		if (strchr(me->value, '/') == NULL) {
		    StrAllocCopy(me->compression_encoding, "application/");
		    StrAllocCat(me->compression_encoding, me->value);
		    if (TRACE)
		        fprintf(stderr,
				"                Treating as: '%s'\n",
				me->compression_encoding);
		} else {
                    StrAllocCopy(me->compression_encoding, me->value);
		}
		break;
	    case CONTENT_LENGTH:
                me->content_length = atoi (me->value);
                /* This is TEMPORARY. */
                loading_length = me->content_length;
                if (TRACE)
                  fprintf (stderr,
                         "[MIME_put_char] Picked up Content-Length: '%d'\n",
                         me->content_length);
                break;
	    case LOCATION:
		StrAllocCopy(redirecting_url, me->value);
                if (TRACE)
                   fprintf(stderr,
                     "[MIME_put_char] Picked up Location: '%s'\n", me->value);
                break;
	    default:		/* Should never get here */
	    	break;
	    }
	} else {
	    if (me->value_pointer < me->value + VALUE_SIZE - 1) {
	        *me->value_pointer++ = c;
		break;
	    } else {
	        goto value_too_long;
	    }
	}
	/* Fall through */
	
    case JUNK_LINE:
        if (c == '\n') {
	    me->state = NEWLINE;
	    me->fold_state = me->state;
	}
	break;
	
	
    } /* switch on state*/
    
    return;
    
value_too_long:
    if (TRACE)
        fprintf(stderr, "HTMIME: *** Syntax error. (string too long)\n");
    
bad_field_name:				/* Ignore it */
    me->state = JUNK_LINE;
    return;
    
}



/*	String handling
**	---------------
**
**	Strings must be smaller than this buffer size.
*/
PRIVATE void HTMIME_put_string ARGS2(HTStream *, me, CONST char*, s)
{
    CONST char * p;

    if (me->state == MIME_TRANSPARENT) {	/* Optimisation */
        (*me->targetClass.put_string)(me->target,s);

    } else if (me->state != MIME_IGNORE) {
        if (TRACE)
	    fprintf(stderr, "HTMIME:  %s\n", s);

        for (p=s; *p; p++)
	    HTMIME_put_character(me, *p);
    }
}


/*	Buffer write.  Buffers can (and should!) be big.
**	------------
*/
PRIVATE void HTMIME_write ARGS3(HTStream *, me, CONST char*, s, int, l)
{
    CONST char * p;

    if (me->state == MIME_TRANSPARENT) {	/* Optimisation */
        (*me->targetClass.put_block)(me->target, s, l);

    } else {
        if (TRACE)
	    fprintf(stderr, "HTMIME:  %s\n", s);

        for (p=s; p < s+l; p++)
	    HTMIME_put_character(me, *p);
    }
}


/*	Free an HTML object
**	-------------------
**
*/
PRIVATE void HTMIME_free ARGS1(HTStream *, me)
{
    if (me->target)
        (*me->targetClass._free)(me->target);
    FREE(me);
}

/*	End writing
*/

PRIVATE void HTMIME_abort ARGS2(HTStream *, me, HTError, e)
{
    if (me->target)
        (*me->targetClass._abort)(me->target, e);
    FREE(me);
}


/*	Structured Object Class
**	-----------------------
*/
PRIVATE CONST HTStreamClass HTMIME =
{		
	"MIMEParser",
	HTMIME_free,
	HTMIME_abort,
	HTMIME_put_character,
	HTMIME_put_string,
	HTMIME_write
}; 


/*	Subclass-specific Methods
**	-------------------------
*/

PUBLIC HTStream* HTMIMEConvert ARGS3(
	HTPresentation *,	pres,
	HTParentAnchor *,	anchor,
	HTStream *,		sink)
{
    HTStream* me;
    
    me = (HTStream *)calloc(1, sizeof(*me));
    if (me == NULL)
        outofmem(__FILE__, "HTML_new");
    me->isa	=	&HTMIME;       
    me->sink	=	sink;
    me->anchor	=	anchor;
    me->target	= 	NULL;
    me->state	=	BEGINNING_OF_LINE;
    /*
     *	Sadly enough, change this to always default to WWW_HTML
     *	to parse all text as HTML for the users.
     *  GAB 06-30-94
     *	Thanks to Robert Rowland robert@cyclops.pei.edu
     *
     *	After discussion of the correct handline, should be application/octet-
     *		stream or unknown; causing servers to send a correct content
     *		type.
     *
     *  The consequence of using WWW_UNKNOWN is that you end up downloading
     *  as a binary file what 99.9% of the time is an HTML file, which should
     *  have been rendered or displayed.  So sadly enough, I'm changing it
     *  back to WWW_HTML, and it will handle the situation like Mosaic does,
     *  and as Robert Rowland suggested, because being functionally correct
     *  99.9% of the time is better than being technically correct but
     *  functionally nonsensical. - FM
     *//***
    me->format	  =	WWW_UNKNOWN;
        ***/
    me->format	  =	WWW_HTML;
    me->targetRep =	pres->rep_out;
    me->boundary  =	NULL;		/* Not set yet */
    me->encoding  =	0;		/* Not set yet */
    me->compression_encoding = NULL;	/* Not set yet */
    me->net_ascii =	NO;		/* Local character set */
    return me;
}

PUBLIC HTStream* HTNetMIME ARGS3(
	HTPresentation *,	pres,
	HTParentAnchor *,	anchor,
	HTStream *,		sink)
{
    HTStream* me = HTMIMEConvert(pres,anchor, sink);
    if (!me)
        return NULL;
    
    me->net_ascii = YES;
    return me;
}
