/*
HTML & CSS washing functionality
  (used to be part of surgeweb level code, now also used by surgealert)

Example of use:
*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include "tcplib.h"
#include "lprintf.h"
#include "lib.h"
#include "thread.h"
#include "debug.h"
#ifdef USE_DFOPEN
#include "dfopen.h"
#endif
#include "buffer.h"
#include "html.h"
#include "parts.h"
#include "css.h"
#include "wash.h"
#include "libwebio.h"

#define PROXY_ADDITION_ONLY 1
#include "../dftp/mail/wproxy.h"



//#include "decode.h"
//#include "sformat.h"
//#include "smail.h"
//#include "parts.h"
//#include "prcc.h"

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE
#define TRUE (!FALSE)
#endif

extern int emsg(const char * arg_list, ...);
extern int dmsg(const char * arg_list, ...);


// Uncomment to track down problems in the html parser
// #define __DO_HTMLPARSER_DEBUG__

#ifdef __DO_HTMLPARSER_DEBUG__
	#define dmsg imsg_surgeweb
#else
	#define html_log(a,b,c,d,e,f) 
#endif


static int (*imsg)(const char *, ...);
void wash_set_imsg(int (*fn)(const char *, ...)){
	imsg = fn;
}
static char* (*wash_tmpfile)(void*,char *);
void wash_set_tmpfile(char* (*fn)(void*,char *)){
	wash_tmpfile = fn;
}
static int (*wash_format_url)(void*,char *,SwPart*,char*,char*);
void wash_set_format_url(int (*fn)(void*,char *,SwPart*,char*,char*)){
	wash_format_url = fn;
}
static int (*wash_load_mpr_cid_index)(void*,Pair**,char*);
void wash_set_load_mpr_cid_index(int (*fn)(void*,Pair**,char*)){
	wash_load_mpr_cid_index = fn;
}
static int (*wash_handle_mpr)(void*,Pair**,char*,Pair*,char*);
void wash_set_handle_mpr(int (*fn)(void*,Pair**,char*,Pair*,char*)){
	wash_handle_mpr = fn;
}


// Basic buffer processing code for html and css

WashHtml *washhtml_new()
{
	WashHtml *b;
	b = calloc(1,sizeof(WashHtml));
	b->buffer = buffer_new(BFSZ);
	b->params=calloc(1,sizeof(WashParams));
	return b;
}
void washhtml_free(WashHtml **b)
{
	if (!b || !*b) return ;
	buffer_free(&(*b)->buffer);
	html_free(&(*b)->parser);
	washcss_free(&((*b)->params->css_parser));
	ffree(((*b)->params->tag_base));
	free((*b)->params);
	free(*b);
	*b=NULL;
}
WashCss *washcss_new()
{
	WashCss *b;
	b = calloc(1,sizeof(WashCss));
	b->buffer = buffer_new(BFSZ);
//	b->params=calloc(1,sizeof(WashParams));
	return b;
}
void washcss_free(WashCss **b)
{
	if (!b || !*b) return ;
	buffer_free(&(*b)->buffer);
	css_free(&(*b)->parser);
	free(*b);
	*b=NULL;
}

Wash *wash_new()
{
	Wash *wh;
	wh = calloc(1,sizeof(Wash));
	return wh;
}
int wash_init(Wash *wh)
{
	return TRUE;
}
void wash_free(Wash **wh)
{
	if (!wh || !*wh) return ;
	pair_free(&(*wh)->outvar);
	pair_free(&(*wh)->param);
	word_free(&(*wh)->cids_found);

	free(*wh);
	*wh=NULL;
}


Pair wash_strip_tags[] = {
	{"meta","content"},
	{"link","href"},
//	{"img","src"},
//	{"body","background"},
//	{"td","background"},
//	{"a","href"},
//	{"base","href"},			// Strip but keep for later
	{NULL,NULL}
};

FILE *wash_fordisplay_html(Wash *wh, FILE *f, int keep_open, int type)
{
	char bf[BFSZ];
	WashHtml *io;
	int len,count=0, result;
	FILE *fout;

	io = wh->html;
	switch(type){
		default:
		case WASH_FULL_HTML:
			io->process = wash_process_fullhtml;
			io->params->fancy_replies=!istrue(pair_find(wh->param,"no_replycolor"));
			break;
		case WASH_STRIP_TO_TEXT:
			io->process = wash_strip_totext;
			break;
		case WASH_CID_ONLY:
			io->process = wash_cid_only;
			break;
		case WASH_SIG_CID:
			io->process = wash_sig_cid;
			break;
		case WASH_STRIP_FOR_SEND:
			io->process = wash_strip_forsend;
			break;
	}
	io->params->block_images = !istrue(pair_find(wh->param,"allow_images"));

	imsg("'wash' the html part type %d - starting", type);

	for (;!feof(f);count++) {
		len = (int)fread(bf,1,BFSZ-1,f);
		result = wash_html_process(wh,bf,len);
		if(result!=TRUE) {
			imsg("Infinite loop exit outer loop too\n");
			break;
		}
	}
	wash_html_process(wh,NULL,0);		// Flush anything that may be left in the buffer
	dmsg("end of wash processing: buffer(external)=%d buffer(internal)=%d",buffer_bytes(io->buffer), io->parser->bytes-io->parser->upto);

	imsg("html wash complete (%d changes made)",io->params->changes);

	fout=io->fout;

	if(keep_open){
		fseek(f,0,SEEK_SET);
	}else{
		fclose(f); f=NULL;
	}
	fflush(fout);
	fseek(fout,0,SEEK_SET);
	return fout;
}

// Generic function to setup html washing processing loops
// NOTE: Hypothetically could be part of the html module and thus have one level less of buffering
//	but this separates out 4 logical entities: 1) Input prcc, 2) buffered io handling (this fn), 3)Data parsing
//	4) What you are going to do with data, which is really quite nice.
int wash_html_process(Wash *wh, char *data, int len)
{
	int flush=FALSE, todo,avail,chunk,n,todo_previous=0;
	int count=1;
	char *p=NULL;

	WashHtml *io = wh->html;

	if (!data){
		todo=0;
		flush=TRUE;
	}else{
		todo=len;
		p=data;
	}

	dmsg("sw_prcc_output %s", flush?"FLUSHING":"");

again:
	// Copy maximum amount of data to our buffer used by processing
	if (todo>0){
		avail = buffer_size(io->buffer) - buffer_bytes(io->buffer);
		chunk = todo>avail ? avail : todo;
		n = (int) buffer_write(p,1,chunk,io->buffer);
		p += chunk;
		todo -= chunk;
#ifdef __DO_HTMLPARSER_DEBUG__
		dmsg("copied %d bytes to processing buffer %d left for next iteration (x%d)",chunk, todo, count);
		dmsg("XXX  buffer(external)=%d [sz=%d] buffer(internal)=%d [sz=%d]", buffer_bytes(io->buffer), buffer_size(io->buffer), (io->parser?io->parser->bytes-io->parser->upto:0), (io->parser?io->parser->size:0));
#endif
		io->params->isend=FALSE;
	}

	// Process as much of our input buffer as we can
	if (flush || buffer_size(io->buffer)==buffer_bytes(io->buffer)){

//		imsg("html washing available buffer");
//		show_buffer_ends(io->parser,io->buffer);	// For serious debugging

		// Do init
		if (!io->parser){
			io->parser = html_new(32000);
			html_setup(io->parser,
				(size_t(*)(char *,size_t,size_t,void *))buffer_read,
				(size_t(*)(const char *,size_t,size_t,void *))fwrite,
				(int(*)(void *))buffer_error,
				(int(*)(void *))buffer_eof);
		}

		// This is quite nice to have a separate function allows one to have multiple different functions for actual data processing
		// (for once only and simple implementations, even nicer to have single function containing everything)
		while(io->process(wh,io->buffer,io->parser,io->fout,io->params) && !io->params->isend) {}

#ifdef __DO_HTMLPARSER_DEBUG__
		dmsg("After processing run buffer length=%d (x%d)",buffer_bytes(io->buffer),count);
		show_buffer_ends(io->parser,io->buffer); 	// For serious debugging
#endif
		if (flush && buffer_bytes(io->buffer)>0){
			if(count==2){
				emsg("surgeweb: Infinite loop prevention hit 1 in wash_html_process [%s]", wh->state);
				return FALSE;
			}
			count++;
			goto again;
		}
		// Note above processing function may leave some data in buffer if html was not "complete" - just output this in case of flush
		if (flush && io->parser->text_len>0){
			io->parser->write(io->parser->raw,1,io->parser->text_len,io->fout);
		}
	}

	// Process any more data we have in the input buffer
	if(todo>0){
		if(todo==todo_previous){
			emsg("surgeweb: Infinite loop prevention hit 2 in wash_html_process [%s]", wh->state);
			//printf("wash Infinite loop prevention hit 2\n");
			return FALSE;
		}
		todo_previous=todo;
		count++;
		goto again;
	}

	return TRUE;
}


// CSS washing too ... *** begin ***
int wash_css_write(Css *css, char *str, int len, FILE *fout)
{
	if(css->ignore) return TRUE;
	if(!css->start_done) {
		css->write("<style>",1,7,fout);
		css->start_done=TRUE;
	}
	css->write(str,1,len,fout);
	css->done_some=TRUE;
	return TRUE;
}

int wash_fordisplay_css(Wash *wh, WashCss **h, FILE *fout, char *data, int len)
{
	// Basic css parser allocation
	if(!*h){
		*h=washcss_new();
		(*h)->fout = fout;
		(*h)->process = wash_process_fullcss;
	}
	// like wash_html_process
	wash_css_process(wh, *h, data, len);
	return TRUE;
}
int wash_css_process(Wash *wh, WashCss *io, char *data, int len)
{
	int flush=FALSE, todo,avail,chunk,n,todo_previous=0;
	int count=1,dbg_loop=0;
	char *p=NULL;

	dmsg("wash_css_process");

	if (!data){
		todo=0;
		flush=TRUE;
	}else{
		todo=len;
		p=data;
	}
	//if(lib_starts_with(data,"||<<<!//!<<||//?||$*$$!!///^)))))>>>>)))))**#~!#**(((((<<<<<<<!//!///////////uuid"))
	//{
	//	int i=1;
	//}


again:
	// Copy maximum amount of input data to our buffer used by processing
	if (todo>0){
		avail = buffer_size(io->buffer) - buffer_bytes(io->buffer);
		chunk = todo>avail ? avail : todo;
		n = (int)buffer_write(p,1,chunk,io->buffer);
		p += chunk;
		todo -= chunk;
//		dmsg("copied %d bytes to processing buffer %d left for next time (x%d)",chunk, todo, count);
	}

	// Process as much of our input buffer as we can
	if (flush || buffer_size(io->buffer)==buffer_bytes(io->buffer)){

//		imsg("css washing available buffer");
//		show_buffer_ends(io->parser,io->buffer);	// For serious debugging

		// Do init
		if (!io->parser){
			io->parser = css_new(BFSZ);
			css_setup(io->parser,
				(size_t(*)(char *,size_t,size_t,void *))buffer_read,
				(size_t(*)(const char *,size_t,size_t,void *))fwrite,
				(int(*)(void *))buffer_error,
				(int(*)(void *))buffer_eof);
		}

		// This is quite nice to have a separate function allows one to have multiple different functions for actual data processing
		// (for once only and simple implementations, even nicer to have single function containing everything)
		while(io->process(wh,io->buffer,io->parser,io->fout)) {
			dbg_loop++;
		}

//		dmsg("After processing run buffer length=%d (x%d)",buffer_bytes(io->buffer),count);
//		show_buffer_ends(io->parser,io->buffer); 	// For serious debugging

		// Note above function may leave some data in buffer if css parsing was not "complete" - do we need to handle this in case of flush??
		if (flush && buffer_bytes(io->buffer)>0){
			if(!wh->warned1){
				emsg("surgeweb: leaving some data in buffer - css probably invalid or corrupt");
				wh->warned1 = TRUE;
			}
		}
	}

	// Process any more data we have in the input buffer
	if(todo>0){
		if(todo==todo_previous){
			if(!wh->warned2){
				emsg("surgeweb: Infinite loop prevention hit in wash_css_process");
				wh->warned2 = TRUE;
			}
			return FALSE;
		}
		todo_previous=todo;
		count++;
		goto again;
	}
	return TRUE;
}
int wash_process_fullcss(void *d, Buffer *buffer, Css *css, FILE *fout)
{
	int retval = TRUE;
	int i, block_images;
	Wash *wh=(Wash*)d;
	
	block_images = !istrue(pair_find(wh->param,"allow_images"));

	i = css_scan_ex(css,buffer);
	switch(i) {
	case CSS_END:
		retval=FALSE;
		break;

	case CSS_MORE:
		retval=FALSE;
		break;

	case CSS_MEDIA_START:
	case CSS_MEDIA_END:
		wash_css_write(css,css->raw,css->raw_len,fout);
		break;


	case CSS_SELECTOR:
		if (css->raw_len>0) {
			if (lib_starts_with(css->raw,".sw_message")){
				css->ignore=TRUE;
			}

			if (!lib_starts_with(css->raw,".sw_message")){
				char *s=".sw_message ";		// old: .mycss
				wash_css_write(css,s,lib_strlen(s),fout);
			}
			wash_css_write(css,css->raw,css->raw_len,fout);
		}
		break;

	case CSS_EOL:
		if(css->done_some) wash_css_write(css,"\r\n",2,fout);
		break;

	case CSS_DECL_END:
		wash_css_write(css,css->raw,css->raw_len,fout);
		css->ignore=FALSE;
		break;

	default:
		// Absolute positioning is a no-go within the surgeweb interface
		if(lib_starts_with(css->raw,"position") && strstr(css->raw,"absolute")!=NULL)
			break;
		// Fixed is bad too
		if(lib_starts_with(css->raw,"position") && strstr(css->raw,"fixed")!=NULL)
			break;
		if(block_images && lib_starts_with(css->raw,"background-image")) 
			css->raw[0] = '_';

		if (css->raw_len && !css->ignore && css->done_some) // chrisp, was & css_done_some bitwise which was probably wrong
			wash_css_write(css,css->raw,css->raw_len,fout);
//		fflush(fout);
		break;
	}
	return retval;
}

// CSS washing code *** end ***

int wash_mpr_show(Wash *wh, Html *h, FILE *fout, WashParams *params, int check)
{
	char bf[BFSZ];
	if(params->no_mpr) {
		if(!check) return FALSE;
		lprintf(bf,BFSZ," [ Image ] ");
		h->write(bf,1,lib_strlen(bf),fout);
		return FALSE;
	}
	if(params->width_limit)	mpair_add(&h->tag.attributes,"class","win_width");
	return TRUE;
}

// More rigorous attempt to stop cross site scripting using event handlers 
// Likely to catch other things too but lets try it and add tags as we find we need them...
// This is specifically to remove event handlers like onmousedown, onclick, onerror, and the zillion other possible ones
char *wash_keep_tags[] = {
	"id","class","style","src","href","alt","target","background",
	"height","width","color","bgcolor","size","border",
	"colspan","rowspan","align","valign","cellspacing","cellpadding","vspace","hspace",
	"cid", "face",	// Our own tag for image tracking
	NULL};
int wash_xss_tag_clean(Wash *wh, Tag *tag)
{
	int i;
	MPair *pp;
	MPairList *tag_orig;

	tag_orig=tag->attributes;
	tag->attributes=NULL;
	for(pp=mpair_first(tag_orig);pp;pp=pp->next){
		for(i=0;wash_keep_tags[i]!=NULL;i++){
			if(strcmpnc(wash_keep_tags[i],pp->var)==0){
				mpair_add_atend(&tag->attributes,pp->var,pp->val);
				break;
			}
		}
	}
	mpair_free(&tag_orig);
	return TRUE;
}
static char *xhtml_strings[]={"HTML_PARAM","HTML_FILE","HTML_BUF","HTML_END",
    "HTML_DOCTYPE","HTML_TAG","HTML_TEXT","HTML_COMMENT",
    "HTML_SPACE","HTML_EOL","HTML_MORE","HTML_INVALID_EMAIL","HTML_SCRIPT"};

#define COUNT (params->changes)++
int wash_process_fullhtml(void *d, Buffer *buffer, Html *h, FILE *fout, WashParams *params)
{
	int retval = TRUE;
	int tok,i,j,len,entity=FALSE,last_was_text,last_was_blockquote,done_some;
	char *p, *s, bf[BFSZ*2],bf2[BFSZ],tmp[BFSZ],*x;
	SwPart *part;
	Wash *wh=(Wash*)d;

	last_was_text=params->last_was_text;
	params->last_was_text=FALSE;
	last_was_blockquote=params->last_was_blockquote;
	params->last_was_blockquote=FALSE;

	params->debug_tag_count++;
//	if(params->debug_tag_count==50000){
//		_asm{int 03};
//	}

	tok = html_scan_ex(h,buffer);
//printf("washhtml: raw %s %d (%.*s)\n",xhtml_strings[tok],h->raw_len,h->raw_len,h->raw);
	html_log(h,"html input :",h->raw,tok,buffer,dmsg);

	// Style blocks may turn up as a variety of HTML tokens
	if (params->instyle && (tok==HTML_COMMENT || tok==HTML_TEXT || tok==HTML_SPACE || tok==HTML_EOL)){ 
		if (tok==HTML_EOL){
			wash_fordisplay_css(wh, &params->css_parser, fout, "\r\n", 2);
		}else{
			wash_fordisplay_css(wh, &params->css_parser, fout, h->raw, h->raw_len);
		}
		return TRUE;
	}
	if (params->inscript && (tok==HTML_COMMENT || tok==HTML_TEXT || tok==HTML_SPACE || tok==HTML_EOL)){
		return TRUE;
	}

	switch(tok) {
	case HTML_DOCTYPE:
		break;

	case HTML_COMMENT:
		dmsg("html comment of %d bytes", h->raw_len);
		if (h->raw_len) 
			h->write(h->raw,1,h->raw_len,fout);
		break;

	case HTML_SCRIPT:
		dmsg("// script of %d bytes stripped", h->raw_len);
		lprintf(bf,BFSZ,"<!-- script of %d bytes stripped -->", h->raw_len);
		if (h->raw_len) 
			h->write(bf,1,lib_strlen(bf),fout);
		break;

	case HTML_SPACE:
	default:
		if (h->raw_len) 
			h->write(h->raw,1,h->raw_len,fout);
		break;

	case HTML_TEXT:
		if(params->inhead) { // might be here???
			break;
		}
		
		// Try and make text links in html clickable if we can - This is a bit of a yuck construct currently, should make nicer but not today...
		if(!params->inlink){
			p=s=h->raw;
			len=h->raw_len;
			// Look for links to make clickable - need a tidier solution really...
			while(len>0){
				if (!(strncmp(p,"http://",7)==0 || strncmp(p,"https://",7)==0 || strncmp(p,"ftp://",6)==0 || strncmp(p,"ftps://",7)==0 || strncmp(p,"www.",4)==0)){
					p++; len--; continue;
				}

				imsg("HTML message link translation to make it clickable (still needs tidying)");
				// Output the stuff we already know about.
				h->write(s,1,p-s,fout);
				// Look for end of link			
				s=p;
				while(len>0&&!(*p==' ' || *p=='\t' || *p=='\0' || *p=='"' || *p=='\'' || *p=='<' || *p=='>')){p++;len--;}
				// Output the link
				if (strncmp(s,"www.",4)==0){
					ncpy(bf,"http://",BFSZ-1);
				}else{
					ncpy(bf,"",BFSZ-1);
				}
				x="<a target=\"_blank\" href=\"";
				h->write(x,1,lib_strlen(x),fout);
				h->write(bf,1,lib_strlen(bf),fout);
				h->write(s,1,p-s,fout);
				x="\">";
				h->write(x,1,lib_strlen(x),fout);
				h->write(s,1,p-s,fout);
				x="</a>";
				h->write(x,1,lib_strlen(x),fout);
				s=p;
			}	
			// Output everything we have left
			h->write(s,1,p-s,fout);
			params->last_was_text=TRUE;
			break;
		}

		// Works fairly well to break up long lengths of html text. I think the j check may not EVER get hit.
		len=j=0;
		p=h->raw;
		for(i=0;i<h->raw_len;i++,len++,j++){
			// Reset on whitespace and preserve email addresses
			if (h->raw[i]==' ')
				j=0;
			// Make sure we preserve stuff that is sensible to preserver eg: &nbsp; etc, email addresses 
			if (h->raw[i]=='&' || h->raw[i]=='@' || h->raw[i]=='.') entity=true;
			if (entity){
				if(h->raw[i]==';' || h->raw[i]==' ' || h->raw[i]=='\t') entity=false;
				j=0;
				continue;
			}

			if (j>15){
				h->write(p,1,len,fout);
				p+=len;
				len=j=0;
				h->write("<wbr>",1,5,fout);
				COUNT;
			}
		}	
		h->write(p,1,len,fout);
		break;
	case HTML_END: 
		params->isend = TRUE;
		retval = TRUE;
		break;
	case HTML_MORE:
		retval = FALSE;
		break;
	case HTML_EOL:
		if(params->keep_eol)
			h->write(" \r\n",1,3,fout);
		else if (last_was_text)
			h->write(" ",1,1,fout);
		break;
	case HTML_TAG:
		// Predefined tags to only strip predefined attributes
		if (!h->tag.isendtag) {
			for(i = 0; wash_strip_tags[i].var; i++) {
				if (strcmpnc(wash_strip_tags[i].var,h->tag.name) != 0) continue;
				mpair_remove(h->tag.attributes,wash_strip_tags[i].val,NULL);
				COUNT;
				break;
			}
			// Do any required cleaning of inline styles, bit of a hack but just make sure position:absolute & relative are not applied
			done_some=FALSE;
			ncpy(bf,mpair_find(h->tag.attributes,"style"),BFSZ-1);
			s=bf;
			if(params->block_images && *s && strstrnc(s,"background") && strstrnc(s, "url")){
				lib_replace(s,bf2,BFSZ,"background","_no_backgr");
				ncpy(bf,bf2,BFSZ-1);
				done_some=TRUE;
				pair_add(&wh->outvar,"images_disabled","true");
			}
			if(*s && strstrnc(s,"position")){
				lib_replace(s,bf2,BFSZ,"position","_no_posn");
				ncpy(bf,bf2,BFSZ-1);
				done_some=TRUE;
			}
			if(*s && strstrnc(s,"text-indent:-")){
				lib_replace(s,bf2,BFSZ,"text-indent:-","_no_-indent:-");
				ncpy(bf,bf2,BFSZ-1);
				done_some=TRUE;
			}
			if(*s && strchr(s,'"')){
				lib_replace(s,bf2,BFSZ,"\"","");
				ncpy(bf,bf2,BFSZ-1);
				done_some=TRUE;
			}
			if(done_some){
				mpair_add(&h->tag.attributes,"style",bf2);
			}
		}

		// Run css through its own parser and washing code
		if (strcmpnc(h->tag.name,"style")==0){
			params->instyle=!h->tag.isendtag;
			if(h->tag.isendtag && params->css_parser){
				wash_fordisplay_css(wh, &params->css_parser, fout, NULL, 0);	// Flush the css parser
				params->css_parser->parser->start_done=FALSE;
			}
			// Allow the stripping out of empty style tags
			if(params->css_parser && params->css_parser->parser) {
				if(h->tag.isendtag){
					if(params->css_parser->parser->done_some) 
						html_write_tag_simple(h,&h->tag,fout);
				}else{
					params->css_parser->parser->done_some=FALSE;
				}
			}
			break;
		}
		
		// Strip script tags
		if (strcmpnc(h->tag.name,"script")==0){
			params->inscript=!h->tag.isendtag;			
			COUNT;
			break;
		}

		// Hackers could inject script, lets completely remove see if anyone complains
		if (strcmpnc(h->tag.name,"iframe")==0){
			COUNT;
			break;
		}

		// Hackers can do redirects, completely remove CHRISP ADDED.
		if (strcmpnc(h->tag.name,"meta")==0){
			COUNT;
			break;
		}


		// Remove background attributes if they contain images
		s=mpair_find(h->tag.attributes,"background");
		if(params->block_images && strstrnc(s,"http:")){
			mpair_add(&h->tag.attributes,"background","");
			COUNT;
		}			

		s=mpair_find(h->tag.attributes,"class");
		if(strstrnc(s,"spacer")){
			mpair_add(&h->tag.attributes,"class","");
			COUNT;
		}			


		// Try remove all ids to see if that improves rendering of some messages without breaking too many others
		s=mpair_find(h->tag.attributes,"id");
		if(lib_strlen(s)>0){
			mpair_add(&h->tag.attributes,"id","");
			COUNT;
		}			

		// Replace any body tags
		if (strcmpnc(h->tag.name,"html")==0 || strcmpnc(h->tag.name,"meta")==0 || strcmpnc(h->tag.name,"title")==0){
			COUNT;
			break;
		}
		if (strcmpnc(h->tag.name,"head")==0){
			params->inhead=!h->tag.isendtag;
			COUNT;
			break;
		}


		// Replace any body tags
		if (strcmpnc(h->tag.name,"body")==0){
			if(params->inhead) params->inhead=FALSE;
			ncpy(bf,mpair_find(h->tag.attributes,"class"),BFSZ-1);						
			mpair_free(&h->tag.attributes);
			ncpy(h->tag.name,"div",NAMELEN-1);
			if (lib_strlen(bf)>0)
				mpair_add(&h->tag.attributes,"class",bf);
			COUNT;
		}
		// Tags with limited stripping
		if (strcmpnc(h->tag.name,"img")==0){
			SwPart *cloc_part;

			s=mpair_findnc(h->tag.attributes,"src");
			cloc_part = parts_findpart_bycloc(wh->parts, s);
		
			// CID based image that is part of this message
			if (lib_starts_with_nc(s,"cid:")){
				part = parts_findpart_bycid(wh->parts, s+4);
				if (part) {
					mpair_add(&h->tag.attributes,"cid",s+4);			// QQQS
					wash_format_url(wh->data,"cid",part,bf,s+4);
					mpair_add(&h->tag.attributes,"src",web_encode(tmp,bf));		// Search: QQMPR
					if(!wash_mpr_show(wh,h,fout,params,TRUE)) break;
					COUNT;
				}
			// Content-Loction based image (kinda like CID)
			}else if( cloc_part && !(lib_starts_with_nc(s,"http://")||lib_starts_with_nc(s,"https://")) ){
				wash_format_url(wh->data,"cloc",cloc_part,bf,NULL);
				mpair_add(&h->tag.attributes,"src",web_encode(tmp,bf));
				if(!wash_mpr_show(wh,h,fout,params,TRUE)) break;
				COUNT;
			// no CID but it is an image that is part of this message
			}else if(!lib_starts_with_nc(s,"http://") && strchr(s,'/')==NULL){
				part = parts_findpart_byname(wh->parts, s);
				if (part) {
					wash_format_url(wh->data,"img",part,bf,NULL);
					mpair_add(&h->tag.attributes,"src",web_encode(tmp,bf));
					if(!wash_mpr_show(wh,h,fout,params,TRUE)) break;
					COUNT;
				}
			// strip javascript
			}else if(lib_starts_with_nc(s,"javascript:")){
				lprintf(bf,BFSZ,"_%s",s);
				mpair_add(&h->tag.attributes,"src",bf);
				COUNT;
			}else{		
				if(lib_starts_with_nc(s,"http://")){
					if(params->block_http){
						pair_add(&wh->outvar,"http_content_blocked","true");
						mpair_add(&h->tag.attributes,"src","");
					} else	if(params->proxy_http){
						char newurl[BFSZ];
						pair_add(&wh->outvar,"http_content_proxied","true");
						wproxy_add(s, newurl, params->ipnum);
						mpair_add(&h->tag.attributes,"src",newurl);
					}
				}
				if(params->block_images){
//					web_var_add(sw->w,"images_disabled","true");
					pair_add(&wh->outvar,"images_disabled","true");
					mpair_add(&h->tag.attributes,"src","");
				}
			}
		}

		// Remember base url
		if (strcmpnc(h->tag.name,"base")==0){
			s=mpair_find(h->tag.attributes,"href");
			params->tag_base=strdup(s);
			COUNT;
			break;
		}

		// Do some more CID replacements
		if (strcmpnc(h->tag.name,"td")==0){
			s=mpair_find(h->tag.attributes,"background");
			if (lib_starts_with_nc(s,"cid:")){
				// look for relevant part
				part = parts_findpart_bycid(wh->parts, s+4);
				if (part) {
					wash_format_url(wh->data,"img",part,bf,NULL);
					mpair_add(&h->tag.attributes,"background",web_encode(tmp,bf));
					if(!wash_mpr_show(wh,h,fout,params,FALSE)) break;
					COUNT;
				}
			}			
		}

		// deal with links relative to base tag etc
		if (strcmpnc(h->tag.name,"a")==0){
			s=mpair_find(h->tag.attributes,"href");
			if(params->tag_base && *s && !lib_starts_with_nc(s,"http://") && !lib_starts_with_nc(s,"https://")){
				web_base_format_url(bf,params->tag_base,s,BFSZ*2);
				mpair_add(&h->tag.attributes,"href",bf);
			}
			if(!h->tag.isendtag){
				mpair_add(&h->tag.attributes,"target","_blank");
				params->inlink=TRUE;
			}else{
				params->inlink=FALSE;
			}
		}

		// Make sure pre stays formatted!
		if (strcmpnc(h->tag.name,"pre")==0){
			params->keep_eol = !h->tag.isendtag;
		}

// We now deal with this fully using CSS
#ifdef _junk_
		// Style blockquotes using our own color scheme
		//	Deal with Outlook Express formatting
		if (strcmpnc(h->tag.name,"blockquote")==0 && !h->tag.isendtag && params->fancy_replies){
			pair_remove(&h->tag.attributes,"style",NULL);
			params->last_was_blockquote=TRUE;
		}
		//  Deal with OSX mail formatitng
		if (strcmpnc(h->tag.name,"span")==0 && last_was_blockquote && !h->tag.isendtag && params->fancy_replies){
			s=pair_find_null(h->tag.attributes,"Apple-style-span");
			if(s){
				pair_remove(&h->tag.attributes,"style",NULL);
			}
		}
#endif
		// More rigorous attempt to stop cross site scripting using event handlers 
		// Likely to catch other things too but lets try it anyway...
		wash_xss_tag_clean(wh,&h->tag);
		
		html_write_tag_simple(h,&h->tag,fout);
		break;
	case HTML_INVALID_EMAIL:
		dmsg("Correcting invalid tag as email address ");
		if (h->raw_len) {
			h->write("&lt;",1,4,fout);	
			h->write(h->raw,1,h->raw_len,fout);
			h->write("&gt;",1,4,fout);
		}
		break;
	}
	dmsg("done one HTML token [%s]",html_strings[tok]);
	return retval;
}

int wash_text_semiflowed_eol(Wash *wh, Html *h, FILE *fout, WashParams *params)
{
	int i;
	if(wh->flowed && !params->done_some){
		for(i=0;i<params->quote_level;i++)
			h->write(">",1,1,fout);
	}
	h->write("\n",1,1,fout);
#ifdef MARIJN_DEBUG
	fflush(fout);
#endif
	params->done_some=FALSE;
	return TRUE;
}
int wash_text_semiflowed_output(Wash *wh, Html *h, FILE *fout, WashParams *params, char *txt, int len)
{
	int i;
	if(len==0) return TRUE;
	//	h->write("\n",1,1,fout);
	if(wh->flowed && !params->done_some){
		for(i=0;i<params->quote_level;i++)
			h->write(">",1,1,fout);
		if(txt[0]=='>')					// Space stuff '>' characters for format=flowed
			h->write(" ",1,1,fout);
		params->done_some=TRUE;
	}
	h->write(txt,1,len,fout);
#ifdef MARIJN_DEBUG
	fflush(fout);
#endif
	return TRUE;
}

// Strip html to plain text
// NEW: would like it to be format-flowed :-)
// impact??
//		FILE *sw_process_for_display(Sw *sw)								<< called from sw_ccmd_msg_text & sw_tcmd_process_message (both reformat html part for display as text)
//		int sformat_prepare_message_textparts_mpa(SFormat *sf, char *body)		<<< format mpa during send
//
// OK just need to make both this function and sw_prcc_mime_encode aware of nesting levels, and it should work :-)
int wash_strip_totext(void *d, Buffer *buffer, Html *h, FILE *fout, WashParams *params)
{
	int retval = TRUE,last_was_text;
	int i,j,len;
	char bf[BFSZ],bf2[BFSZ],*lbf=bf, *lbf2=bf2;
	Wash *wh=(Wash*)d;

	dmsg("html stripping available buffer");

	last_was_text=params->last_was_text;
	params->last_was_text=FALSE;

	i = html_scan_ex(h,buffer);
	html_log(h,"html input :",h->raw,i,buffer,dmsg);
	switch(i) {

	default:
		break;

	case HTML_TEXT:		
		if(h->raw_len>BFSZ-1){
			lbf=calloc(1,h->raw_len+1);
			lbf2=calloc(1,h->raw_len+1);
		}
		ncpy_ok(lbf,h->raw,h->raw_len); // do not warn about bfsz+1 errors... as bfsz is not relevant... 
		// Make sure things like &gt; area actually displayed as '>'
		// Any decoding results in a string that is the same size or smaller than input string
		len=web_decode(lbf2, lbf, h->raw_len+1);

		params->last_was_div=FALSE;
		if(params->instyle) {
			if(lbf!=bf){ffree(lbf);ffree(lbf2);}
			return retval;
		}
		wash_text_semiflowed_output(wh,h,fout,params,lbf2,len);
		params->last_was_text=TRUE;
		params->last_text_char=lbf2[len-1];

		if(lbf!=bf){ffree(lbf);ffree(lbf2);}
		break;
	case HTML_END: 
		params->isend = TRUE;
	case HTML_MORE:
		retval = FALSE;
		break;
	case HTML_EOL:
		// Need to add spaces on html line boundaries if following a text part that did not end on a space
		//  notably firefox breaks input text into multiple lines and eats spaces
		//  (possibly still need to tweak so only does at the end of a line if next token is text??)
		if(last_was_text && params->last_text_char && params->last_text_char!=' ' && params->last_text_char!='\\')
			wash_text_semiflowed_output(wh,h,fout,params," ", 1);
		
		// Keep line boundaries on preformatted text
		if(params->keep_eol)
			wash_text_semiflowed_output(wh,h,fout,params,"\r\n", 2);

		break;
	case HTML_SPACE:
		if(params->is_whitespace_pre){
			char tmp[BFSZ];
			tmp[0]=0;
			for(j=0;j<h->raw_len;j++){
				ncat(tmp,"    ",BFSZ-1);
			}
			wash_text_semiflowed_output(wh,h,fout,params,tmp, lib_strlen(tmp));
		}else{
			wash_text_semiflowed_output(wh,h,fout,params," ", 1);
		}
		break;
	case HTML_TAG:
		if (strcmpnc(h->tag.name,"style")==0){
			params->instyle=!h->tag.isendtag;
			return retval;
		}	
		if (strcmpnc(h->tag.name,"br")==0 && !params->last_was_div){
			wash_text_semiflowed_eol(wh,h,fout,params);
		}
		if (strcmpnc(h->tag.name,"p")==0 && h->tag.isendtag){
			wash_text_semiflowed_eol(wh,h,fout,params);
		}
		if (strcmpnc(h->tag.name,"div")==0 && !h->tag.isendtag){
			wash_text_semiflowed_eol(wh,h,fout,params);
			params->last_was_div=TRUE;
		}else{
			params->last_was_div=FALSE;
		}
		if (strcmpnc(h->tag.name,"span")==0){
			char *s = mpair_find(h->tag.attributes,"class");
			if(!h->tag.isendtag && strcmpnc(s,"Apple-tab-span")==0){
				params->is_whitespace_pre=TRUE;
			}else{
				params->is_whitespace_pre=FALSE;
			}
		}

		if (strcmpnc(h->tag.name,"blockquote")==0 && wh->flowed){
			if(!h->tag.isendtag){
				params->quote_level++;
				wash_text_semiflowed_eol(wh,h,fout,params);
			}else{
				params->quote_level--;
			}
		}

		// Make sure pre stays formatted!
		if (strcmpnc(h->tag.name,"pre")==0){
			if(!h->tag.isendtag) 
				wash_text_semiflowed_eol(wh,h,fout,params);
			params->keep_eol = !h->tag.isendtag;
		}
		break;
	}
	dmsg("done html stripping available buffer");
	return retval;
}

int wash_cid_only(void *d, Buffer *buffer, Html *h, FILE *fout, WashParams *params)
{
	int retval = TRUE;
	int i, last_was_text,done_some;
	char *s, bf[BFSZ],bf2[BFSZ],tmp[BFSZ];
	SwPart *part;
	Wash *wh=(Wash*)d;

	last_was_text=params->last_was_text;
	params->last_was_text=FALSE;

	dmsg("html washing available buffer");

	i = html_scan_ex(h,buffer);
	html_log(h,"html input :",h->raw,i,buffer,dmsg);

	if (params->inscript && (i==HTML_COMMENT || i==HTML_TEXT || i==HTML_SPACE || i==HTML_EOL)){
		return retval;
	}

	switch(i) {

	default:
		if (h->raw_len) 
			h->write(h->raw,1,h->raw_len,fout);
		break;

	case HTML_EOL:
		if(params->keep_eol)
			h->write(" \r\n",1,3,fout);
		else if (last_was_text)
			h->write(" ",1,1,fout);
		break;
	case HTML_END: 
		params->isend = TRUE;
	case HTML_MORE:
		retval = FALSE;
		break;
	case HTML_TEXT:
		if (h->raw_len) 
			h->write(h->raw,1,h->raw_len,fout);
		params->last_was_text=TRUE;
		break;

	case HTML_TAG:
		if (!h->tag.isendtag) {
			// Do any required cleaning of inline styles, bit of a hack but just make sure position:absolute & relative are not applied
			done_some=FALSE;
			ncpy(bf,mpair_find(h->tag.attributes,"style"),BFSZ-1);
			s=bf;
			if(*s && strchr(s,'"')){
				lib_replace(s,bf2,BFSZ,"\"","");
				ncpy(bf,bf2,BFSZ-1);
				done_some=TRUE;
			}
			if(done_some){
				mpair_add(&h->tag.attributes,"style",bf2);
			}
		}


		// Strip script tags
		if (strcmpnc(h->tag.name,"script")==0){
			params->inscript=!h->tag.isendtag;			
			COUNT;
			break;
		}

		// Hackers could inject script, lets completely remove see if anyone complains
		if (strcmpnc(h->tag.name,"iframe")==0){
			COUNT;
			break;
		}

		if (strcmpnc(h->tag.name,"img")==0){
			s=mpair_find(h->tag.attributes,"src");			// QQSIG wash SHOW
			// Primary CID replacement => url
			if (lib_starts_with_nc(s,"cid:")){
				// look for relevant part
				part = parts_findpart_bycid(wh->parts, s+4);
				if (part) {		// search: QQQ_IMGURL
					wash_format_url(wh->data,"cid",part,bf,NULL);
					mpair_add(&h->tag.attributes,"src",web_encode(tmp,bf));
					if(!wash_mpr_show(wh,h,fout,params,TRUE)) break;
					COUNT;
				}
			} else if (lib_starts_with_nc(s,"rsig:")){
				// turn whatever we have decided on for a SIGFORMAT into a displayable URL
				// with extra parameters to be able to id part
				wash_format_url(wh->data,"rsig",NULL,bf,s+5);
				mpair_add(&h->tag.attributes,"src",web_encode(tmp,bf));
			}

			if(!lib_starts_with_nc(s,"http://") || !lib_starts_with_nc(s,"http2://")){
				// look for relevant part
				part = parts_findpart_bycloc(wh->parts, s+4);
				if (part) {
					wash_format_url(wh->data,"cloc",part,bf,NULL);
					mpair_add(&h->tag.attributes,"src",web_encode(tmp,bf));
					if(!wash_mpr_show(wh,h,fout,params,TRUE)) break;
					COUNT;
				}
			}
		}
		// Do some more CID replacements
		if (strcmpnc(h->tag.name,"td")==0){
			s=mpair_find(h->tag.attributes,"background");
			if (lib_starts_with_nc(s,"cid:")){
				// look for relevant part
				part = parts_findpart_bycid(wh->parts, s+4);
				if (part) {
					wash_format_url(wh->data,"cloc",part,bf,NULL);
					mpair_add(&h->tag.attributes,"background",web_encode(tmp,bf));
					if(!wash_mpr_show(wh,h,fout,params,FALSE)) break;
					COUNT;
				}
			}
		}
		// Make sure pre stays formatted!
		if (strcmpnc(h->tag.name,"pre")==0){
			params->keep_eol = !h->tag.isendtag;
		}

		// More rigorous attempt to stop cross site scripting using event handlers 
		// Likely to catch other things too but lets try it anyway...
		wash_xss_tag_clean(wh,&h->tag);

		html_write_tag_simple(h,&h->tag,fout);
		break;
	}
	dmsg("done one HTML TOKEN (2)");
	return retval;
}

// must be a better function that does just htis already but cant find it so this will do :-)
void wash_url_decode(char *src, Pair **components)
{
	char *s, bf[BFSZ], param[BFSZ];
	char *want[] = {"rcid","rsig","remo","sig_id","img_id",NULL};
	int i;
	
	for(i=0;want[i];i++){
		lprintf(param,BFSZ,"&amp;%s=", want[i]);	
		s=strstr(src,param);
		if(!s) continue;
		ncpy(bf,s+lib_strlen(param),BFSZ-1);
		s=strstr(bf,"&amp;");
		if(s) *s=0;
		pair_add(components,want[i],bf);
	}
}

// Clean html for sending
int wash_strip_forsend(void *d, Buffer *buffer, Html *h, FILE *fout, WashParams *params)
{
	int retval = TRUE;
	int i,last_was_text,done;
	char *src, bf[BFSZ], tmp[BFSZ], cid[BFSZ], *s;
	static int new_cid_counter=0;
	Pair *components=NULL;
	Wash *wh=(Wash*)d;
	int isEmo, isSig;

	last_was_text=params->last_was_text;

	dmsg("html stripping for sending buffer");

	i = html_scan_ex(h,buffer);
	html_log(h,"html input :",h->raw,i,buffer,dmsg);
	switch(i) {

	case HTML_TEXT:
	case HTML_SPACE:
		if (h->raw_len){ 
			h->write(h->raw,1,h->raw_len,fout);
			params->last_text_char=h->raw[h->raw_len-1];
			params->last_was_text=TRUE;
		}
		break;
		
	default:
		if (h->raw_len){ 
			h->write(h->raw,1,h->raw_len,fout);
		}
		break;

	case HTML_EOL:
		if(params->keep_eol){
			h->write(" \r\n",1,3,fout);
		}else{
			int last=params->last_text_char;
			if (params->last_was_text && last && !(last==' ' || last==':' || last=='>'))
				h->write(" ",1,1,fout);
		}
		break;
	case HTML_END: 
		params->isend = TRUE;
	case HTML_MORE:
		retval = FALSE;
		break;
	case HTML_TAG:
		// Make sure pre stays formatted!
		if (strcmpnc(h->tag.name,"pre")==0){
			params->keep_eol = !h->tag.isendtag;
		}

        if (strcmpnc(h->tag.name,"div")==0){
            if(h->tag.isendtag){
                if(params->insig>0){
                    params->insig--;
                }
            } else {
                s=mpair_find(h->tag.attributes,"id");
                if (strcmpnc(s, "editor_signature")==0 || params->insig>0) {
                    params->insig++;
                }
            }
        }
        if (strcmpnc(h->tag.name,"blockquote")==0){
            if(h->tag.isendtag){
                if(params->inblock>0){
                    params->inblock--;
                }
            } else {
                params->inblock++;
            }
        }

		// Deal with the complicated issue of image tags...		QQMPR QQQQCID 	// QQSIG wash SEND
		if (strcmpnc(h->tag.name,"img")==0){
            int strip_image;
			char *attach_mid = pair_find(wh->param,"attach_mid");
			src=mpair_find(h->tag.attributes,"src");
				
			// First decode the url to see if it is really some form of MPR image
			wash_url_decode(src,&components);

			isEmo = lib_strlen(pair_find(components,"remo"))>0;
			isSig = lib_strlen(pair_find(components,"rsig"))>0;

			// image tags can come from a variety of sources:
			//	- manually added; result of a "forward"; image MPR addition; smiley emoticon; signature image
			// identify and process any MPR images 
			if(isEmo || isSig){			// is it a sig or emoticon
				// These need actual attachment file additon to message temp directory as well
				wash_handle_mpr(wh->data,&wh->cid_index,src,components,cid);	// << generates cid 
			}else if(s=pair_find_null(components,"rcid")){	// generic mpr attachment
				// newer format needed as attribute based approach was not workable for mpr handling
				ncpy(cid,s,BFSZ-1);
			}else{
				// Older format using attribute - keep for surgemail binary compatibility
				ncpy(cid,mpair_find(h->tag.attributes,"cid"),BFSZ-1);
			}
			pair_free(&components);


			// Possibly make this setting based. For now on any kind of "reply" strip mpr based images 
			//  these do remain attached if you forward the message
			s=pair_find(wh->param,"send_type");
            
            // Try and strip images for replies of any kind (except for images we have added on this message)
            strip_image = (strcmp(s,"reply")==0 || strcmp(s,"reply_all")==0);
            if(isEmo || isSig) strip_image = false;
            if(params->insig || !params->inblock) strip_image = false;
            
			if(strip_image){
				lprintf(bf,BFSZ," [ Image ] ");
				h->write(bf,1,lib_strlen(bf),fout);
				break;
			}

			done=FALSE;
			// Check whether mpr part with existing cid or new mpr part that has no cid yet
			if(lib_strlen(cid)>0){
				wash_load_mpr_cid_index(wh->data,&wh->cid_index,pair_find(wh->param,"attach_mid"));
				if(pair_find_value(wh->cid_index,cid)){
					lprintf(bf,BFSZ,"cid:%s",cid);
					mpair_add(&h->tag.attributes,"src",bf);		// Search: QQMPR
					done=TRUE;
				}
				mpair_removenc(h->tag.attributes,"cid",bf);
			}
			
			if(!done && (lib_strlen(cid)>0)){
				// need to assign a new cid
				pair_add(&wh->cid_index,"id","CID");
				mpair_add(&h->tag.attributes,"src","cid:CID");
			}

			word_add(&wh->cids_found, cid);
			
//			// Stripping images to [text]		// QQQS
			s=mpair_find(h->tag.attributes,"link_id");
			if(lib_strlen(s)>0){
				char cid[BFSZ];
				wash_load_mpr_cid_index(wh->data,&wh->cid_index,pair_find(wh->param,"attach_mid"));
				lprintf(cid,BFSZ,"%d%d%d@%s",int_time(NULL),th_id(),new_cid_counter++,pair_find(wh->param,"domain"));
				lprintf(bf,BFSZ,"cid:%s",cid);
				mpair_add(&h->tag.attributes,"src",bf);
				lprintf(tmp,BFSZ,"link_id=%s",s);
				s=pair_find_value(wh->cid_index,tmp);
				if(!s) break;
				pair_add(&wh->cid_index,s,cid);
			}
			mpair_remove(h->tag.attributes,"inline_image",NULL);
			mpair_remove(h->tag.attributes,"image_name",NULL);
			mpair_remove(h->tag.attributes,"class",NULL);
		}
		html_write_tag_simple(h,&h->tag,fout);
		break;
	}
	dmsg("done html stripping available buffer for sending");
	return retval;
}

// Signature tidyup...
void sw_sig_found(void *sw, char *rsig);
int wash_sig_cid(void *d, Buffer *buffer, Html *h, FILE *fout, WashParams *params)
{
	int retval = TRUE;
	int i, last_was_text;
	char *s, bf[BFSZ],tmp[BFSZ],*rsig;
	Wash *wh=(Wash*)d;
	Pair *components=NULL;

	last_was_text=params->last_was_text;
	params->last_was_text=FALSE;

	dmsg("html washing available buffer");

	i = html_scan_ex(h,buffer);
	html_log(h,"html input :",h->raw,i,buffer,dmsg);

	if (params->inscript && (i==HTML_COMMENT || i==HTML_TEXT || i==HTML_SPACE || i==HTML_EOL)){
		return retval;
	}

	switch(i) {

	default:
		if (h->raw_len) 
			h->write(h->raw,1,h->raw_len,fout);
		break;

	case HTML_EOL:
		if(params->keep_eol)
			h->write(" \r\n",1,3,fout);
		else if (last_was_text)
			h->write(" ",1,1,fout);
		break;
	case HTML_END: 
		params->isend = TRUE;
	case HTML_MORE:
		retval = FALSE;
		break;
	case HTML_TEXT:
		if (h->raw_len) 
			h->write(h->raw,1,h->raw_len,fout);
		params->last_was_text=TRUE;
		break;

	case HTML_TAG:
		if (strcmpnc(h->tag.name,"img")==0){	// QQSIG wash sig
			s=mpair_find(h->tag.attributes,"src");
				
			// need to, restore existing urls  
			wash_url_decode(s,&components);
			rsig = pair_find(components,"rsig");
			lprintf(bf,BFSZ,"rsig:%s",rsig);

			if(lib_strlen(rsig)>0){
				mpair_add(&h->tag.attributes,"src",web_encode(tmp,bf));
				COUNT;
			}

			// somehow store the attachment ids we have found for later review
			sw_sig_found(wh->data,rsig);
		}
		// Make sure pre stays formatted!
		if (strcmpnc(h->tag.name,"pre")==0){
			params->keep_eol = !h->tag.isendtag;
		}

		html_write_tag_simple(h,&h->tag,fout);
		break;
	}
	dmsg("done one HTML TOKEN (2)");
	return retval;
}
