Skip to content
Snippets Groups Projects
js_global.c 97.2 KiB
Newer Older
/* js_global.c */

/* Synchronet JavaScript "global" object properties/methods for all servers */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2008 Rob Swindell - http://www.synchro.net/copyright.html		*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#define JS_THREADSAFE	/* needed for JS_SetContextThread */

#include "sbbs.h"
#include "base64.h"
#define MAX_ANSI_SEQ	16
#define MAX_ANSI_PARAMS	8
typedef struct {
	scfg_t				*cfg;
	jsSyncMethodSpec	*methods;
} private_t;

/* Global Object Properites */
enum {
	 GLOB_PROP_ERRNO
};

static JSBool js_system_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    jsint       tiny;
	JSString*	js_str;

    tiny = JSVAL_TO_INT(id);

	switch(tiny) {
			JS_NewNumberValue(cx,ERROR_VALUE,vp);
			break;
		case GLOB_PROP_ERRNO:
			JS_NewNumberValue(cx,errno,vp);
			break;
			if((js_str=JS_NewStringCopyZ(cx, strerror(errno)))==NULL)
				return(JS_FALSE);
	        *vp = STRING_TO_JSVAL(js_str);
	return(JS_TRUE);
#define GLOBOBJ_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_SHARED
static jsSyncPropertySpec js_global_properties[] = {
/*		 name,			tinyid,					flags,			ver */
	{	"errno"			,GLOB_PROP_ERRNO		,GLOBOBJ_FLAGS, 310 },
	{	"errno_str"		,GLOB_PROP_ERRNO_STR	,GLOBOBJ_FLAGS, 310 },
	{	"socket_errno"	,GLOB_PROP_SOCKET_ERRNO	,GLOBOBJ_FLAGS, 310 },
	JSRuntime*		runtime;
	JSContext*		cx;
	JSContext*		parent_cx;
	JSObject*		obj;
	JSScript*		script;
	msg_queue_t*	msg_queue;
	js_branch_t		branch;
} background_data_t;

static void background_thread(void* arg)
	background_data_t* bg = (background_data_t*)arg;
	msgQueueAttach(bg->msg_queue);
	if(!JS_ExecuteScript(bg->cx, bg->obj, bg->script, &result)
		&& JS_GetProperty(bg->cx, bg->obj, "exit_code", &exit_code))
		result=exit_code;
	js_EvalOnExit(bg->cx, bg->obj, &bg->branch);
	js_enqueue_value(bg->cx, bg->msg_queue, result, NULL);
	JS_DestroyScript(bg->cx, bg->script);
static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	background_data_t* bg;

	if((bg=(background_data_t*)JS_GetContextPrivate(cx))==NULL)
		return;

	/* Use parent's context private data */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(bg->parent_cx));

	/* Call parent's error reporter */
	bg->error_reporter(cx, message, report);

	/* Restore our context private data */
	JS_SetContextPrivate(cx, bg);
}

static JSBool js_BranchCallback(JSContext *cx, JSScript* script)
{
	background_data_t* bg;

	if((bg=(background_data_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	if(bg->parent_cx!=NULL && !JS_IsRunning(bg->parent_cx)) 	/* die when parent dies */
		return(JS_FALSE);

	return js_CommonBranchCallback(cx,&bg->branch);
static JSBool
js_log(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	JSBool retval;
	background_data_t* bg;

	if((bg=(background_data_t*)JS_GetContextPrivate(cx))==NULL)
		return JS_FALSE;

	/* Use parent's context private data */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(bg->parent_cx));

	/* Call parent's log() function */
	retval = bg->log(cx, obj, argc, argv, rval);

	/* Restore our context private data */
	JS_SetContextPrivate(cx, bg);

	return retval;
}

/* Create a new value in the new context with a value from the original context */
/* Note: objects (including arrays) not currently supported */
static jsval* js_CopyValue(JSContext* cx, jsval val, JSContext* new_cx, jsval* rval)
{
	*rval = JSVAL_VOID;

	if(cx==new_cx
		|| JSVAL_IS_BOOLEAN(val) 
		|| JSVAL_IS_NULL(val) 
		|| JSVAL_IS_VOID(val) 
		|| JSVAL_IS_INT(val))
		*rval = val;
	else if(JSVAL_IS_NUMBER(val)) {
		jsdouble	d;
		if(JS_ValueToNumber(cx,val,&d))
			JS_NewNumberValue(new_cx,d,rval);
	}
	else {
		JSString*	str;
		size_t		len;
		char*		p;

		if((p=js_ValueToStringBytes(cx,val,&len)) != NULL
			&& (str=JS_NewStringCopyN(new_cx,p,len)) != NULL)
			*rval=STRING_TO_JSVAL(str);
	}

	return rval;
}

static JSBool
js_load(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char		path[MAX_PATH+1];
    uintN		i;
	uintN		argn=0;
    const char*	filename;
    JSScript*	script;
	private_t*	p;
	JSObject*	exec_obj;
	JSContext*	exec_cx=cx;
	background_data_t* bg;
deuce's avatar
deuce committed
	jsrefcount	rc;
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);
	exec_obj=JS_GetScopeChain(cx);

	if(JSVAL_IS_BOOLEAN(argv[argn]))
		background=JSVAL_TO_BOOLEAN(argv[argn++]);

	if(background) {

		if((bg=(background_data_t*)malloc(sizeof(background_data_t)))==NULL)
		memset(bg,0,sizeof(background_data_t));
		bg->parent_cx = cx;
		/* Setup default values for branch settings */
		bg->branch.limit=JAVASCRIPT_BRANCH_LIMIT;
		bg->branch.gc_interval=JAVASCRIPT_GC_INTERVAL;
		bg->branch.yield_interval=JAVASCRIPT_YIELD_INTERVAL;
		if(JS_GetProperty(cx, obj,"js",&val))	/* copy branch settings from parent */
			memcpy(&bg->branch,JS_GetPrivate(cx,JSVAL_TO_OBJECT(val)),sizeof(bg->branch));
		bg->branch.terminated=NULL;	/* could be bad pointer at any time */
		bg->branch.counter=0;
		bg->branch.gc_attempts=0;
		if((bg->runtime = jsrt_GetNew(JAVASCRIPT_MAX_BYTES, 1000, __FILE__, __LINE__))==NULL)
			return(JS_FALSE);

	    if((bg->cx = JS_NewContext(bg->runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
			return(JS_FALSE);
		if((bg->obj=js_CreateCommonObjects(bg->cx
				,p->cfg			/* common config */
				,NULL			/* additional global methods */
				,0				/* uptime */
				,""				/* hostname */
				,""				/* socklib_desc */
				,&bg->branch	/* js */
				,NULL			/* client */
				,INVALID_SOCKET	/* client_socket */
		bg->msg_queue = msgQueueInit(NULL,MSG_QUEUE_BIDIR);

		js_CreateQueueObject(bg->cx, bg->obj, "parent_queue", bg->msg_queue);

		/* Save parent's error reporter (for later use by our error reporter) */
		bg->error_reporter=JS_SetErrorReporter(cx,NULL);
		JS_SetErrorReporter(cx,bg->error_reporter);
		JS_SetErrorReporter(bg->cx,js_ErrorReporter);
		/* Set our branch callback (which calls the generic branch callback) */
		JS_SetContextPrivate(bg->cx, bg);
		JS_SetBranchCallback(bg->cx, js_BranchCallback);
		/* Save parent's 'log' function (for later use by our log function) */
		if(JS_GetProperty(cx, obj, "log", &val)) {
			JSFunction* func;
			if((func=JS_ValueToFunction(cx, val))!=NULL && !(func->flags&JSFUN_INTERPRETED)) {
				bg->log=func->u.n.native;
				JS_DefineFunction(bg->cx, bg->obj
					,"log", js_log, func->nargs, func->flags);
			}
		}

		exec_cx = bg->cx;
		exec_obj = bg->obj;
	} else if(JSVAL_IS_OBJECT(argv[argn])) {
		JSObject* tmp_obj=JSVAL_TO_OBJECT(argv[argn++]);
		if(!JS_ObjectIsFunction(cx,tmp_obj))	/* Scope specified */
			exec_obj=tmp_obj;
	}
	if(argn==argc) {
		JS_ReportError(cx,"no filename specified");
		return(JS_FALSE);
	}
	if((filename=js_ValueToStringBytes(cx, argv[argn++], NULL))==NULL)
		return(JS_FALSE);

		if((js_argv=JS_NewArrayObject(exec_cx, 0, NULL)) == NULL)
			return(JS_FALSE);
		JS_DefineProperty(exec_cx, exec_obj, "argv", OBJECT_TO_JSVAL(js_argv)
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

		for(i=argn; i<argc; i++)
			JS_SetElement(exec_cx, js_argv, i-argn, js_CopyValue(cx,argv[i],exec_cx,&val));

		JS_DefineProperty(exec_cx, exec_obj, "argc", INT_TO_JSVAL(argc-argn)
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
	if(isfullpath(filename))
		strcpy(path,filename);
	else {
		sprintf(path,"%s%s",p->cfg->mods_dir,filename);
		if(p->cfg->mods_dir[0]==0 || !fexistcase(path))
			sprintf(path,"%s%s",p->cfg->exec_dir,filename);
	JS_ClearPendingException(exec_cx);
	if((script=JS_CompileFile(exec_cx, exec_obj, path))==NULL)
		*rval = OBJECT_TO_JSVAL(js_CreateQueueObject(cx, obj, NULL, bg->msg_queue));
		success = _beginthread(background_thread,0,bg)!=-1;
		success = JS_ExecuteScript(exec_cx, exec_obj, script, rval);
		JS_DestroyScript(exec_cx, script);
	}
}

static JSBool
js_format(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	if((p=js_sprintf(cx, 0, argc, argv))==NULL) {
		JS_ReportError(cx,"js_sprintf failed");
		return(JS_FALSE);
	}

	str = JS_NewStringCopyZ(cx, p);
	if(str==NULL)
		return(JS_FALSE);

	*rval = STRING_TO_JSVAL(str);
    return(JS_TRUE);
static JSBool
js_yield(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
deuce's avatar
deuce committed
	jsrefcount	rc;

	if(argc)
		JS_ValueToBoolean(cx, argv[0], &forced);
	if(forced) {
		YIELD();
	} else {
		MAYBE_YIELD();
	}
static JSBool
js_mswait(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
deuce's avatar
deuce committed
	jsrefcount	rc;
		JS_ValueToInt32(cx,argv[0],&val);
	mswait(val);
	JS_NewNumberValue(cx,msclock()-start,rval);

static JSBool
js_random(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	int32 val=100;

	if(argc)
		JS_ValueToInt32(cx,argv[0],&val);

	JS_NewNumberValue(cx,sbbs_random(val),rval);
	return(JS_TRUE);
}

static JSBool
js_time(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	JS_NewNumberValue(cx,time(NULL),rval);
static JSBool
js_beep(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
deuce's avatar
deuce committed
	jsrefcount	rc;
		JS_ValueToInt32(cx,argv[0],&freq);
		JS_ValueToInt32(cx,argv[1],&dur);
	sbbs_beep(freq,dur);
	return(JS_TRUE);
}

static JSBool
js_exit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	if(argc)
		JS_DefineProperty(cx, obj, "exit_code", argv[0]
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

static JSBool
js_crc16(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
deuce's avatar
deuce committed
	jsrefcount	rc;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((p=js_ValueToStringBytes(cx, argv[0], &len))==NULL)
	return(JS_TRUE);
}

static JSBool
js_crc32(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
deuce's avatar
deuce committed
	uint32_t	cs;
	jsrefcount	rc;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((p=js_ValueToStringBytes(cx, argv[0], &len))==NULL)
deuce's avatar
deuce committed
	cs=crc32(p,len);
deuce's avatar
deuce committed
	JS_NewNumberValue(cx,cs,rval);
	return(JS_TRUE);
}

static JSBool
js_chksum(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	ulong		sum=0;
	char*		p;
deuce's avatar
deuce committed
	jsrefcount	rc;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((p=js_ValueToStringBytes(cx, argv[0], &len))==NULL)
	rc=JS_SUSPENDREQUEST(cx);	/* 3.8 seconds on Deuce's computer when len==UINT_MAX/8 */
	JS_NewNumberValue(cx,sum,rval);
	return(JS_TRUE);
}

static JSBool
js_ascii(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	char		str[2];
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if(JSVAL_IS_STRING(argv[0])) {	/* string to ascii-int */

		if((p=JS_GetStringBytes(JSVAL_TO_STRING(argv[0])))==NULL) 
		*rval=INT_TO_JSVAL(*p);
		return(JS_TRUE);
	}

	/* ascii-int to str */
	JS_ValueToInt32(cx,argv[0],&i);
	str[0]=(uchar)i;
	if((js_str = JS_NewStringCopyZ(cx, str))==NULL)
		return(JS_FALSE);

	*rval = STRING_TO_JSVAL(js_str);
	return(JS_TRUE);
}

static JSBool
js_ctrl(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char		ch;
	char*		p;
	char		str[2];
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if(JSVAL_IS_STRING(argv[0])) {	

		if((p=JS_GetStringBytes(JSVAL_TO_STRING(argv[0])))==NULL) 
			return(JS_FALSE);
		ch=*p;
	} else {
		JS_ValueToInt32(cx,argv[0],&i);
		ch=(char)i;
	}
	str[1]=0;

	if((js_str = JS_NewStringCopyZ(cx, str))==NULL)
		return(JS_FALSE);

	*rval = STRING_TO_JSVAL(js_str);
	return(JS_TRUE);
}

static JSBool
js_ascii_str(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((p=js_ValueToStringBytes(cx, argv[0], NULL))==NULL)
	str = JS_NewStringCopyZ(cx, buf);
	free(buf);
	if(str==NULL)
static JSBool
js_strip_ctrl(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((p=js_ValueToStringBytes(cx, argv[0], NULL))==NULL) 
	js_str = JS_NewStringCopyZ(cx, buf);
	free(buf);
	if(js_str==NULL)
		return(JS_FALSE);

	*rval = STRING_TO_JSVAL(js_str);
	return(JS_TRUE);
}

static JSBool
js_strip_exascii(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	char*		p;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((p=js_ValueToStringBytes(cx, argv[0], NULL))==NULL) 
	js_str = JS_NewStringCopyZ(cx, buf);
	free(buf);
	if(js_str==NULL)
		return(JS_FALSE);

	*rval = STRING_TO_JSVAL(js_str);
	return(JS_TRUE);
}

static JSBool
js_lfexpand(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	ulong		i,j;
	char*		inbuf;
	char*		outbuf;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((inbuf=js_ValueToStringBytes(cx, argv[0], NULL))==NULL)
	if((outbuf=(char*)malloc((strlen(inbuf)*2)+1))==NULL)
		return(JS_FALSE);

	for(i=j=0;inbuf[i];i++) {
		if(inbuf[i]=='\n' && (!i || inbuf[i-1]!='\r'))
			outbuf[j++]='\r';
		outbuf[j++]=inbuf[i];
	}
	outbuf[j]=0;

	str = JS_NewStringCopyZ(cx, outbuf);
static int get_prefix(char *text, int *bytes, int *len, int maxlen)
{
	int		tmp_prefix_bytes,tmp_prefix_len;
	int		expect;
	int		depth;

	*bytes=0;
	*len=0;
	tmp_prefix_bytes=0;
	tmp_prefix_len=0;
	depth=0;
	expect=1;
	if(text[0]!=' ')
		expect=2;
	while(expect) {
		tmp_prefix_bytes++;
		/* Skip CTRL-A codes */
		while(text[tmp_prefix_bytes-1]=='\x01') {
			tmp_prefix_bytes++;
			if(text[tmp_prefix_bytes-1]=='\x01')
				break;
			tmp_prefix_bytes++;
		}
		tmp_prefix_len++;
		if(text[tmp_prefix_bytes-1]==0 || text[tmp_prefix_bytes-1]=='\n' || text[tmp_prefix_bytes-1]=='\r')
			break;
		switch(expect) {
			case 1:		/* At start of possible quote (Next char should be space) */
				if(text[tmp_prefix_bytes-1]!=' ')
					expect=0;
				else
					expect++;
				break;
			case 2:		/* At start of nick (next char should be alphanum or '>') */
			case 3:		/* At second nick initial (next char should be alphanum or '>') */
			case 4:		/* At third nick initial (next char should be alphanum or '>') */
				if(text[tmp_prefix_bytes-1]==' ' || text[tmp_prefix_bytes-1]==0)
					expect=0;
				else
					if(text[tmp_prefix_bytes-1]=='>')
						expect=6;
					else
						expect++;
				break;
			case 5:		/* After three regular chars, next HAS to be a '>') */
				if(text[tmp_prefix_bytes-1]!='>')
					expect=0;
				else
					expect++;
				break;
			case 6:		/* At '>' next char must be a space */
				if(text[tmp_prefix_bytes-1]!=' ')
					expect=0;
				else {
					expect=1;
					*len=tmp_prefix_len;
					*bytes=tmp_prefix_bytes;
					depth++;
					/* Some editors don't put double spaces in between */
					if(text[tmp_prefix_bytes]!=' ')
						expect++;
				}
				break;
			default:
				expect=0;
				break;
		}
	}
	if(*bytes >= maxlen) {
		lprintf(LOG_CRIT, "Prefix bytes %u is larger than buffer (%u) here: %*.*s",*bytes,maxlen,maxlen,maxlen,text);
static void outbuf_append(char **outbuf, char **outp, char *append, int len, int *outlen)
{
	char	*p;

	/* Terminate outbuf */
	**outp=0;
	/* Check if there's room */
	if(*outp - *outbuf + len < *outlen) {
		memcpy(*outp, append, len);
		*outp+=len;
		return;
	}
	/* Not enough room, double the size. */
	*outlen *= 2;
	p=realloc(*outbuf, *outlen);
	if(p==NULL) {
		/* Can't do it. */
		*outlen/=2;
		return;
	}
	/* Set outp for new buffer */
	*outp=p+(*outp - *outbuf);
	*outbuf=p;
	memcpy(*outp, append, len);
	*outp+=len;
	return;
}

static int compare_prefix(char *old_prefix, int old_prefix_bytes, char *new_prefix, int new_prefix_bytes)
{
	int i;

	if(new_prefix_bytes != old_prefix_bytes) {
		if(new_prefix_bytes < old_prefix_bytes) {
			if(memcmp(old_prefix, new_prefix, new_prefix_bytes)!=0)
				return(-1);
			for(i=new_prefix_bytes; i<old_prefix_bytes; i++) {
				if(!isspace(old_prefix[i]))
					return(-1);
			}
		}
		else {
			if(memcmp(old_prefix, new_prefix, old_prefix_bytes)!=0)
				return(-1);
			for(i=old_prefix_bytes; i<new_prefix_bytes; i++) {
				if(!isspace(new_prefix[i]))
					return(-1);
			}
		}
		return(0);
	}
	if(memcmp(old_prefix,new_prefix,new_prefix_bytes)!=0)
		return(-1);

	return(0);
}

static JSBool
js_word_wrap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
deuce's avatar
deuce committed
	long		i,k,t;
	uchar*		inbuf;
	char*		outbuf;
	char*		prefix=NULL;
	int			prefix_len=0;
	int			prefix_bytes=0;
	int			quote_count=0;
deuce's avatar
deuce committed
	jsrefcount	rc;
	if(JSVAL_IS_VOID(argv[0]))
		return(JS_TRUE);

	if((inbuf=js_ValueToStringBytes(cx, argv[0], NULL))==NULL) 
	outbuf_size=strlen(inbuf)*3+1;
	if((outbuf=(char*)malloc(outbuf_size))==NULL)

	if(argc>1)
		JS_ValueToInt32(cx,argv[1],&len);

	if(argc>2)
		JS_ValueToInt32(cx,argv[2],&oldlen);

	if(argc>3 && JSVAL_IS_BOOLEAN(argv[3]))
		handle_quotes=JSVAL_TO_BOOLEAN(argv[3]);

	if((linebuf=(char*)malloc((len*2)+2))==NULL) /* room for ^A codes ToDo: This isn't actually "enough" room */
		if((prefix=(char *)malloc((len*2)+2))==NULL) { /* room for ^A codes ToDo: This isn't actually "enough" room */
			free(linebuf);
		prefix[0]=0;
	/* Get prefix from the first line (ouch) */
	l=0;
	i=0;
	if(handle_quotes && (quote_count=get_prefix(inbuf, &prefix_bytes, &prefix_len, len*2+2))!=0) {
		if(prefix_len>len/3*2) {
			/* This prefix is insane (more than 2/3rds of the new width) hack it down to size */
			/* Since we're hacking it, we will always end up with a hardcr on this line. */
			/* ToDo: Something prettier would be nice. */
			sprintf(prefix," %d> ",quote_count);
			prefix_len=strlen(prefix);
			prefix_bytes=strlen(prefix);
		}
deuce's avatar
deuce committed
		else {
			memcpy(prefix,inbuf,prefix_bytes);
deuce's avatar
deuce committed
			/* Terminate prefix */
			prefix[prefix_bytes]=0;
		}
		memcpy(linebuf,prefix,prefix_bytes);
		l=prefix_bytes;
		ocol=prefix_len+1;
		icol=prefix_len+1;
		old_prefix_bytes=prefix_bytes;
	}
	for(; inbuf[i]; i++) {
		if(l>=len*2+2) {
			l-=4;
			linebuf[l]=0;
			lprintf(LOG_CRIT, "Word wrap line buffer exceeded... munging line %s",linebuf);
		}
		switch(inbuf[i]) {
			case '\r':
				crcount++;
				break;
			case '\n':
				if(handle_quotes && (quote_count=get_prefix(inbuf+i+1, &prefix_bytes, &prefix_len, len*2+2))!=0) {
					/* Move the input pointer offset to the last char of the prefix */
					i+=prefix_bytes;
				}
				if(!inbuf[i+1]) {			/* EOF */
					linebuf[l++]='\r';
					linebuf[l++]='\n';
					outbuf_append(&outbuf, &outp, linebuf, l, &outbuf_size);
				/* If there's a new prefix, it is a hardcr */
				else if(compare_prefix(prefix, old_prefix_bytes, inbuf+i+1-prefix_bytes, prefix_bytes)!=0) {
					if(prefix_len>len/3*2) {
						/* This prefix is insane (more than 2/3rds of the new width) hack it down to size */
						/* Since we're hacking it, we will always end up with a hardcr on this line. */
						/* ToDo: Something prettier would be nice. */
						sprintf(prefix," %d> ",quote_count);
						prefix_len=strlen(prefix);
						prefix_bytes=strlen(prefix);
					}
deuce's avatar
deuce committed
					else {
						memcpy(prefix,inbuf+i+1-prefix_bytes,prefix_bytes);
deuce's avatar
deuce committed
						/* Terminate prefix */
						prefix[prefix_bytes]=0;
					}
					linebuf[l++]='\r';
					linebuf[l++]='\n';
					outbuf_append(&outbuf, &outp, linebuf, l, &outbuf_size);
					memcpy(linebuf,prefix,prefix_bytes);
					l=prefix_bytes;
					ocol=prefix_len+1;
					old_prefix_bytes=prefix_bytes;
				}
				else if(isspace(inbuf[i+1]) && inbuf[i+1] != '\n' && inbuf[i+1] != '\r') {	/* Next line starts with whitespace.  This is a "hard" CR. */
					linebuf[l++]='\r';
					linebuf[l++]='\n';
					outbuf_append(&outbuf, &outp, linebuf, l, &outbuf_size);
					l=prefix_bytes;
					ocol=prefix_len+1;
				}
				else {
					if(icol < oldlen) {			/* If this line is overly long, It's impossible for the next word to fit */
						/* k will equal the length of the first word on the next line */
						for(k=0; inbuf[i+1+k] && (!isspace(inbuf[i+1+k])); k++);
						if(icol+k+1 < oldlen) {	/* The next word would have fit but isn't here.  Must be a hard CR */
							linebuf[l++]='\r';
							linebuf[l++]='\n';
							outbuf_append(&outbuf, &outp, linebuf, l, &outbuf_size);
								memcpy(linebuf,prefix,prefix_bytes);
deuce's avatar
deuce committed
							ocol=prefix_len+1;
						}
						else {		/* Not a hard CR... add space if needed */
							if(l<1 || !isspace(linebuf[l-1])) {