#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>


#include "ffget.h"
#include "pldstr.h"
#include "libmime-decoders.h"
#include "MIME_headers.h"



// Debug precodes
#define MIMEH_DPEDANTIC ((glb.debug >= _MIMEH_DEBUG_PEDANTIC))
#define MIMEH_DNORMAL   ((glb.debug >= _MIMEH_DEBUG_NORMAL  ))



struct MIMEH_globals {

	int doubleCR;
	char doubleCRname[_MIMEH_STRLEN_MAX +1];

	char *headerline;
	int save_headers;
	int test_mailbox;
	int debug;
	int webform;
	int doubleCR_count;
	int verbose;
	char output_dir[_MIMEH_STRLEN_MAX +1];

	FILE *header_file;
};

static struct MIMEH_globals glb;


int MIMEH_init( void )
{
	glb.doubleCR = 0;
	glb.headerline = NULL;
	glb.save_headers = 0;
	glb.test_mailbox = 0;
	glb.debug = 0;
	glb.webform = 0;
	glb.doubleCR_count = 0;
	glb.verbose = 0;
	glb.output_dir[0]='\0';
	glb.doubleCRname[0]='\0';

	return 0;
}

int MIMEH_get_doubleCR( void )
{
	return glb.doubleCR;
}

int MIMEH_set_doubleCR( int level )
{
	glb.doubleCR = level;

	return glb.doubleCR;
}

char *MIMEH_get_doubleCR_name( void )
{
	return glb.doubleCRname;
}


int MIMEH_set_debug( int level )
{
	glb.debug = level;
	return glb.debug;
}

int MIMEH_set_outputdir( char *dir )
{
	if (dir) snprintf(glb.output_dir,_MIMEH_STRLEN_MAX,"%s",dir);
	return 0;
}

int MIMEH_set_webform( int level )
{
	glb.webform = level;
	return glb.webform;
}


int MIMEH_set_mailbox( int level )
{
	glb.test_mailbox = level;
	return level;
}

int MIMEH_set_verbosity( int level )
{
	glb.verbose = level;
	return level;
}





/*------------------------------------------------------------------------
Procedure:     MIMEH_set_headers_save ID:1
Purpose:       Sets MIMEH's headers save file (where MIMEH will save the
headers it reads in from the mailpack)
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int MIMEH_set_headers_save( FILE *f )
{
	glb.header_file = f;
	glb.save_headers = 1;
	return 0;
}

int MIMEH_set_headers_nosave( void )
{
	glb.header_file = NULL;
	glb.save_headers = 0;
	return 0;
}

int MIMEH_get_headers_save( void )
{
	return glb.save_headers;
}


int MIMEH_save_doubleCR( FFGET_FILE *f )
{
	char c;
	FILE *fo;
	struct stat st;


	// Determine a file name we can use.

	do {
		glb.doubleCR_count++;
		snprintf(glb.doubleCRname,_MIMEH_STRLEN_MAX,"%s/doubleCR.%d",glb.output_dir,glb.doubleCR_count);
	}
	while (stat(glb.doubleCRname, &st) == 0);


	fo = fopen(glb.doubleCRname,"w");
	if (!fo)
	{
		syslog(1,"MIMEH_save_doubleCR: Error, unable to open '%s' to write (%s)",glb.doubleCRname,strerror(errno));
		return -1;
	}

	if (glb.verbose)	fprintf(stdout,"Saving DoubleCR header: %s\n",glb.doubleCRname);

	while (1)
	{
		c = FFGET_fgetc(f);
		fprintf(fo,"%c",c);
		if ((c == EOF)||(c == '\n'))
		{
			break;
		}
	}

	fclose(fo);

	return 0;
}


/*------------------------------------------------------------------------
Procedure:     MIMEH_read_headers ID:1
Purpose:       Reads from the stream F until it detects a From line, or a blank line
(end of headers)
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int MIMEH_read_headers( FFGET_FILE *f )
{
	char line[_MIMEH_STRLEN_MAX+1];
	int totalsize=0;
	int linesize=0;
	int result = 0;
	int firstline = 1;
	int hl = 0;
	char *tmp;
	char *fget_result = NULL;
	char *headerline_end;
	char *p;

	glb.headerline = NULL;

	if (MIMEH_DNORMAL) fprintf(stdout,"DEBUG: MIME_headers: Starting read\n");


	while ((fget_result=FFGET_fgets(line,_MIMEH_STRLEN_MAX, f)))
	{

		if (MIMEH_DNORMAL) fprintf(stdout,"DEBUG: MIME_headers: [blank = %d] Read: \n%s",f->trueblank,line);

		linesize = strlen(line);
		totalsize += linesize;
		tmp = realloc(glb.headerline, totalsize+1);

		if (!tmp)
		{
			syslog(1,"MIMEH_read_headers(): cannot allocate %d bytes ",totalsize);
			free(glb.headerline);
			glb.headerline = NULL;
			return -1;
		}


		if (!glb.headerline)
		{
			glb.headerline = tmp;
			PLD_strncpy(glb.headerline, line, (linesize +1));
			headerline_end = glb.headerline +totalsize;
		}
		else
		    {

				// Perform header unfolding by removing any CRLF's
				//	of the last line if the first characters of the
				//	newline are blank/space

			if (isspace(*line))
			{
				p = glb.headerline +hl -1;
				while (( *p == '\n' )||( *p == '\r' ))
				{
					p--;
					totalsize--;
				}
			}

			hl = totalsize -linesize;
			glb.headerline = tmp;
			memcpy((glb.headerline +hl), line, (linesize+1));
			//			strcat(glb.headerline,line);
										}


		if (f->trueblank)
		{
			MDECODE_decode_ISO( glb.headerline, totalsize  );

			if (MIMEH_DNORMAL) fprintf(stdout,"DEBUG: MIME_headers: True BLANK LINE located, terminating header read\n");
			if ((glb.save_headers)&&(glb.headerline))
			{
				fprintf(glb.header_file,"%s",glb.headerline);
			}
			if (MIMEH_DNORMAL) 			fprintf(stdout,"%s:%d------------------headers:\n%s\n(TRUE-END Found)\n",__FILE__,__LINE__,glb.headerline);
			result = 1;
			break;
		}


		// If there was a doubleCR at the end of the line,
		//	then we need to save the next set of data until there
		//	is a \n

		if (FFGET_doubleCR)
		{
			MIMEH_save_doubleCR(f);
			FFGET_doubleCR = 0;
			FFGET_SDL_MODE = 0;
			glb.doubleCR = 1;
		}

		firstline = 0;
	}


	// If FFGET ran out of data whilst processing the headers, then acknowledge this
	// by returning a -1.
	//
	// NOTE - This does not mean we do not have any data!
	//  it just means that our input ran out.

	if (!fget_result)
	{
		result = -1;
	}

	//	fprintf(stderr,"DEBUG:Exiting MIME_read_headers()\n");
	return result;
}



/*------------------------------------------------------------------------
Procedure:     MIMEH_display_info ID:1
Purpose:       DEBUGGING - Displays the values of the hinfo structure to
stderr
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int MIMEH_display_info( struct MIMEH_header_info *hinfo )
{
	if (hinfo)
	{
		fprintf(stdout,"\
Content Type = %d\n\
Boundary = %s\n\
Filename = %s\n\
    name = %s\n\
Encoding = %d\n\
Disposit = %d\n\
"\
,hinfo->content_type\
,hinfo->boundary\
,hinfo->filename\
,hinfo->name\
,hinfo->content_transfer_encoding\
,hinfo->content_disposition);
		fflush(stdout);
	}
	return 0; // I was a dilly to use return 1 as std... totally against normal C programming methods!

}




/*------------------------------------------------------------------------
Procedure:     MIMEH_parse_filename ID:1
Purpose:       Reads in a given filename, and cleans it up, removes any risky
components
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int MIMEH_parse_filename( char *dest, char *raw )
{
	char c = '\0';
	char *p, *q, *n;
	int qp_encode = 0;
	int quoted = 0;
	char tmp[_MIMEH_STRLEN_MAX+1];

	/* Locate the delimeter point and keep the character which is at
	that point in a temporary variable. We terminate the string
	at the delimeter point using a \0.

	Later, once we have done what we need to do, we replace the \0
	termination with the original character
	*/

	n = strpbrk(raw,"\n\r;");
	if (n)
	{
		c = *n;
		*n = '\0';
	}

	PLD_strncpy(tmp,raw,_MIMEH_STRLEN_MAX);
	p = tmp;

	if (PLD_strncasecmp(p,"\"3d",3)==0)
	{
		qp_encode = 1;
		quoted=1;
		p+=3;
	}
	else
		if (PLD_strncasecmp(p,"3d\"",3)==0)
		{
			qp_encode = 1;
			quoted = 1;
			p+=3;
		}
		else
			if (PLD_strncasecmp(p,"3d",2)==0)
			{
				qp_encode = 1;
				quoted=0;
				p+=2;
			}
			else
				if ((*p) == '\"')
				{
					qp_encode = 0;
					quoted = 1;
					p++;
				}


	if ((qp_encode)&&(strstr(p,"=OA="))) qp_encode = 1;
	else qp_encode = 0;

	snprintf(dest,_MIMEH_FILENAMELEN_MAX,"%s",p);

//	if (qp_encode) MIMEH_decode_qp(dest);
	if (qp_encode) MDECODE_decode_qp_ISO( dest );

	if (quoted)
	{
		q = strrchr(dest,'\"');
		if (q) *q = '\0';
	}

	if (n) *n = c;

	return 0;
}




/*------------------------------------------------------------------------
Procedure:     MIMEH_parse_headers ID:1
Purpose:       Reads an open filestream in and parses the headers for it.
Input:
Output:
Errors:
------------------------------------------------------------------------*/
int   MIMEH_parse_headers( FFGET_FILE *f, struct MIMEH_header_info *hinfo )
{
	char *safeh, *h, *hl, *safehl;
	char *p,*q, c, *pre_p;
	char *lba;
	char *line_end;
	char *next_angle;
	int result;
	int headerlength;

	if (MIMEH_DNORMAL) fprintf(stdout,"MIMEH_parse_headers: Start [F=%p, hinfo=%p]\n", f, hinfo);

	FFGET_set_watch_SDL(1);
	result = MIMEH_read_headers(f);
	FFGET_set_watch_SDL(0);

//	fprintf(stdout,"%s:%d: Converting to non-ISO encoded data\n",__FILE__,__LINE__ );
//	MDECODE_decode_ISO( glb.headerline, strlen( glb.headerline) );

	hinfo->filename[0] = '\0';
	hinfo->name[0] = '\0';

	safeh = h = glb.headerline;

	// If we ran out of input whilst looking at headers, then, we basically
	// flag this, free up the headers, and return.

	if (result == -1)
	{
		if (glb.headerline) free(glb.headerline);
		return result;
	}

	if (!glb.headerline)
	{
		if (MIMEH_DNORMAL) 		fprintf(stderr,"DEBUG:%s:%d: null glb.headerline\n",__FILE__,__LINE__);
		return 1;
	}


	// Duplicate string
	headerlength = strlen(h);
	safehl = hl = malloc(sizeof(char) *(headerlength+1));
	PLD_strncpy(hl,h, headerlength+1);


	PLD_strlower(hl);

	if (MIMEH_DNORMAL) 	fprintf(stdout,"DEBUG:%s:%d: Header length = %d\n",__FILE__,__LINE__,headerlength);





	// CONTENT TYPE -------------------------------
	// CONTENT TYPE -------------------------------
	// CONTENT TYPE -------------------------------


	hinfo->content_type = _CTYPE_UNKNOWN;

	p = strstr(hl,"content-type");
	if (p)
	{
		p = p +strlen("content-type");
		q = strpbrk(p,"\n\r;");
		if (q)
		{
			c = *q;
			*q = '\0';

			if (strstr(p,"multipart/")) hinfo->content_type = _CTYPE_MULTIPART;
			else
			    if (strstr(p,"text/")) hinfo->content_type = _CTYPE_TEXT;
			else
			    if (strstr(p,"message/rfc822")) hinfo->content_type = _CTYPE_RFC822;
			else
			    if (strstr(p,"/octet-stream")) hinfo->content_type = _CTYPE_OCTECT;
			else
			    if (strstr(p,"/ms-tnef")) hinfo->content_type = _CTYPE_TNEF;
			else hinfo->content_type = _CTYPE_UNKNOWN;

			*q = c;
		}
		else
		{
			syslog(1,"MIMEH_parse_headers(): Cannot locate end of Content-Type specifier!");
		}
	}








	// CONTENT LOCATION -------------------------------
	// CONTENT LOCATION -------------------------------
	// CONTENT LOCATION -------------------------------

	p = strstr(hl,"content-location:");
	if (p)
	{
		if (MIMEH_DNORMAL) 		fprintf(stdout,"%s:%d: Content Location line found - '%s'\n",__FILE__,__LINE__,p);
		q = strrchr(p,'/');
		if (!q) q = strrchr(p,':');
		if (q)
		{
			p = h +(q -hl);
			MIMEH_parse_filename(hinfo->filename, p);
			snprintf(hinfo->name,sizeof(hinfo->name),"%s",hinfo->filename);

		}
		else hinfo->filename[0] = '\0';
	}







	// ATTACHMENT FILENAME ----------------------------
	// ATTACHMENT FILENAME ----------------------------
	// ATTACHMENT FILENAME ----------------------------

	p = strstr (hl, "filename");
	if (p)
	{
		char *string = NULL;

		if (MIMEH_DNORMAL) fprintf (stdout, "MIME_headers: %d: %s", __LINE__, hl);

		string = p + strlen ("filename");

		// skip any spaces

		//		while (*string == ' ')
		while (isspace( *string ))
		{
			string++;
		}

		if ( *string != '=' )
		{
			if (MIMEH_DNORMAL) fprintf(stdout, "MIME_headers:%d:%s: In filename parsing, was expecting a '=' in the start of '%s'\n", __LINE__, __FILE__, string );
		}
		else {

			// we are at the '=' in the header, so skip it
			string++;

			// skip any spaces... again
			//while (*string == ' ')
			while ( isspace( *string ) )
			{
				string++;
			}

			/* Now that we've got the filename string, we need to check it to
			ensure that we are not handling anything potentially dangerous

			Also, we need to decode any ISO components within it too ( this is done
				within the parsing )

			Note that we're sending the pointer from the -original- headers.
			*/

			p = (string -hl) +h;	// Get original ( non lowercase ) string position

			MIMEH_parse_filename (hinfo->filename, p); // p was string prior
			snprintf (hinfo->name, sizeof (hinfo->name), "%s", hinfo->filename);
		}

	}
	else {
		hinfo->filename [0] = '\0';
	}




	// ATTACHMENT -NAME ----------------------------
	// ATTACHMENT -NAME ----------------------------
	// ATTACHMENT -NAME ----------------------------

	if (hinfo->filename[0] == '\0')
	{
		char *string = NULL;

		p = strstr (hl, "name");

		/* 	If we've found just the token 'name' on its own and
		not part of something else like 'myname' or 'filename'
		*/
		if ( (p) && ( (isspace (* (p - 1))) || ( *(p-1) == ';' ) ) )
		{
			string = p + strlen ("name");

			// The character immediately after 'name' should either be a 'space' or a = if
			//	we're detecting a filename.

			if ( (*string == '=') || (isspace(*string)) )
			{

				/*
				Jump through and clean out any white-space in the string segment supposedly
				containing a file name - This is to deal with situations where the MUA might
				have inadvertedly added \t formfeeds and goodness knows what to the line
				*/

				while (isspace(*string))
				{
					string++;
				}

				if ( *string != '=' )
				{
					if (MIMEH_DNORMAL) fprintf( stdout, "MIME_headers:%d:%s: In attachment name parsing, was expecting a '=' in the start of '%s'", __LINE__, __FILE__, string );
				}
				else {

					// we are at the '=' in the header, so skip it

					string++;

					// skip any spaces... again
			//while (*string == ' ')

					while (isspace(*string))
					{
						string++;
					}

					// Because HTML in MIME is so common these days, it's not
			// unusual to get a <META .... name="..."> tag in the headers
			// hence, it's a good idea to text for the trailing > (which
			// is not a legal filechar anyhow!) and REJECT if it contains
			// it

					next_angle = line_end = NULL;
					next_angle = strchr (string, '>');
					if (next_angle) line_end=strpbrk(p,"\n\r\";");
					if ( (!next_angle) || (line_end && (line_end < next_angle)) )
					{
						p = ( string -hl ) +h;
						MIMEH_parse_filename(hinfo->name, p);
					}
				} // If the filename string is formatted correctly

			} // If the character immediately after 'name' was a space or '='

		} // If the 'name' portion was a valid header for a name specification and not a substring

	} // If there currently is no filename associated with this pass of the headers



	// If we got a filename in one, but not the other hinfo-> seach, then
	// Duplicate

	if ((hinfo->filename[0] == '\0')&&(hinfo->name[0] != '\0'))
	{
		snprintf(hinfo->filename,_MIMEH_FILENAMELEN_MAX,"%s",hinfo->name);
	}




	// CONTENT TRANSFER ENCODING ---------------------
	// CONTENT TRANSFER ENCODING ---------------------
	// CONTENT TRANSFER ENCODING ---------------------


	p = strstr(hl,"content-transfer-encoding");
	if (p)
	{
		p = p +strlen("content-transfer-encoding");
		q = strpbrk(p,"\n\r;");
		if (q)
		{
			c = *q;
			*q = '\0';

			if (strstr(p,"base64"))
			{
				hinfo->content_transfer_encoding = _CTRANS_ENCODING_B64;
			}
			else
			    if (strstr(p,"7bit"))
			{
				hinfo->content_transfer_encoding = _CTRANS_ENCODING_7BIT;
			}
			else
			    if (strstr(p,"8bit"))
			{
				hinfo->content_transfer_encoding = _CTRANS_ENCODING_8BIT;
			}
			else
			    if (strstr(p,"quoted-printable"))
			{
				hinfo->content_transfer_encoding = _CTRANS_ENCODING_QP;
			}
			else
			    if (strstr(p,"uuencode"))
			{
				hinfo->content_transfer_encoding = _CTRANS_ENCODING_UUENCODE;
			}
			else hinfo->content_transfer_encoding = _CTRANS_ENCODING_RAW;

			*q = c;
		}
		else
		{
			syslog(1,"MIMEH_parse_headers(): Cannot locate end of Content-Transfer-Encoding specifier!");
		}
	}
	else hinfo->content_transfer_encoding = _CTRANS_ENCODING_RAW;







	// CONTENT DISPOSITION ------------------------------
	// CONTENT DISPOSITION ------------------------------
	// CONTENT DISPOSITION ------------------------------

	p = strstr(hl,"content-disposition");
	if (p)
	{
		p = p +strlen("content-disposition");
		q = strpbrk(p,"\n\r;");
		if (q)
		{
			c = *q;
			*q = '\0';

			if (strstr(p,"inline")) hinfo->content_disposition = _CDISPOSITION_INLINE;
			else
			    if (strstr(p,"form-data"))
			{
				hinfo->content_disposition = _CDISPOSITION_FORMDATA;
				hinfo->content_type = _CDISPOSITION_FORMDATA;
			}
			else
			    if (strstr(p,"attachment")) hinfo->content_disposition = _CDISPOSITION_ATTACHMENT;
			else hinfo->content_disposition = _CDISPOSITION_UNKNOWN;

			*q = c;
		}
		else
		{
			syslog(1,"MIMEH_parse_headers(): Cannot locate end of Content-Disposition specifier!");
		}
	}





	// Seeking out boundaries is not quite as simple as it seems.
	// we can "initially" think that we can just do a "strstr", but
	// what if there is a filename="someboundary=OAxxx.gif" then
	// we're in trouble!
	//

	lba = hl;
	p = strstr(lba, "boundary=");
	while ( (lba != NULL) && (hinfo->boundary_located==0)  && ( p != NULL) )
	{
		if (MIMEH_DNORMAL) fprintf(stdout,"DEBUG:MIME_headers:%s:%d:Looking for headers...(%s)\n",__FILE__,__LINE__,(p-2));

		if (p == NULL) break;

		// Setup a pointer to the char just before this boundary= location

		if (p > hl)
		{
			pre_p = p -1;
		}
		else pre_p = NULL;


		// If the previous char from 'p' is not a ; or "space", then it's not possible for this
		// to be a BOUNDARY

		if ( (pre_p) && (!isspace(*pre_p)) && (*pre_p != ';') )
		{
			if (MIMEH_DNORMAL) fprintf(stdout,"DEBUG:%s:%d:Previous char '%c' is NOT blank, move along now...\n",__FILE__,__LINE__, *pre_p);
			p = strstr(p +strlen("boundary="),"boundary=");
			continue;
		}

		//		fprintf(stdout,"Testing boundary at: %s",p);
		p = p +strlen("boundary=");
		p = h +(p -hl);

		// If we have a quoted boundary, shift one char to the right
		if ((*p) == '\"') p++;

		// Try to find the end of the boundary by looking for ", \n, \r \t or ;
		q = strpbrk(p,"\"\n\r\t;");

		// If we did find something for the end of the boundary
		if (q)
		{
			// If the first char of the boundary isn't a normal alphanumeric char
			if (!(isprint(*p)))
			{

				// Move our start point (for detecting boundaries in the headers)
				// alone one more char...
				lba++;

				// Try find a new boundary...
				p = strstr(lba,"boundary=");
				continue;
			}
			else
			    {

				// if the character was valid, then we have detected a boundary
				// specifier!.  Copy this to the hinfo structure, and set the
				// "located" flag

				// get the character which we stopped on
				c = *q;

				// turn the location into a string terminator
				*q = '\0';


				snprintf(hinfo->boundary,_MIMEH_STRLEN_MAX,"%s",p);
				hinfo->boundary_located = 1;
				*q = c;
			}

			// Set our string terminator location back to its original character
			// (if we dont, then any further string operations wont see beyond
			// it)

		}
		else
		{
			syslog(1,"MIMEH_parse_headers(): Cannot locate end of boundary specifier!");
		}
	} // while


	if (safehl) free(safehl);
	else syslog(1,"%s:%d:WARNING - Unable to free HEADERS allocated memory\n",__FILE__,__LINE__);//	if (hlorg) free(hlorg);


	if (safeh)
	{
		free(safeh);
		glb.headerline = NULL;
	}
	else syslog(1,"%s:%d:WARNING - Unable to free HEADERS allocated memory\n",__FILE__,__LINE__);

	if (MIMEH_DNORMAL) fprintf(stdout,"MIMEH_parse_headers: END [F=%p, hinfo=%p]\n", f, hinfo);

	return 0;
}


//----------------------END
