Skip to content
Snippets Groups Projects
js_file.c 87.7 KiB
Newer Older
/* Synchronet JavaScript "File" Object */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 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										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"
#include "xpendian.h"
	#include <conio.h>      /* for kbhit() */
	FILE* fp;
	char name[MAX_PATH + 1];
	char mode[7];
	uchar etx;
	BOOL debug;
	BOOL rot13;
	BOOL yencoded;
	BOOL uuencoded;
	BOOL b64encoded;
	BOOL network_byte_order;
	BOOL pipe;          /* Opened with popen() use pclose() to close */
	ini_style_t ini_style;

} private_t;

static void dbprintf(BOOL error, private_t* p, char* fmt, ...)
{
	va_list argptr;
	if (p == NULL || (!p->debug && !error))
	va_start(argptr, fmt);
	vsnprintf(sbuf, sizeof(sbuf), fmt, argptr);
	sbuf[sizeof(sbuf) - 1] = 0;
	va_end(argptr);
	lprintf(LOG_DEBUG, "%04d File %s%s", p->fp ? fileno(p->fp) : 0, error ? "ERROR: ":"", sbuf);
/* Converts fopen() style 'mode' string into open() style 'flags' integer */
static int fopenflags(char *mode)
{
	if (strchr(mode, 'b'))
		flags |= O_BINARY;
	if (strchr(mode, 'x'))
		flags |= O_EXCL;
	if (strchr(mode, 'w')) {
		flags |= O_CREAT | O_TRUNC;
		if (strchr(mode, '+'))
			flags |= O_RDWR;
	if (strchr(mode, 'a')) {
		flags |= O_CREAT | O_APPEND;
		if (strchr(mode, '+'))
			flags |= O_RDWR;
	if (strchr(mode, 'r')) {
		if (strchr(mode, '+'))
			flags |= O_RDWR;
js_open(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	BOOL       shareable = FALSE;
	int        file = -1;
	uintN      i;
	jsint      bufsize = 2 * 1024;
	JSString*  str;
	private_t* p;
	jsrefcount rc;

	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	SAFECOPY(p->mode, "w+");     /* default mode */
	for (i = 0; i < argc; i++) {
		if (JSVAL_IS_STRING(argv[i])) {  /* mode */
			if ((str = JS_ValueToString(cx, argv[i])) == NULL)
				return JS_FALSE;
			JSSTRING_TO_STRBUF(cx, str, p->mode, sizeof(p->mode), NULL);
		}
		else if (JSVAL_IS_BOOLEAN(argv[i]))  /* shareable */
			shareable = JSVAL_TO_BOOLEAN(argv[i]);
		else if (JSVAL_IS_NUMBER(argv[i])) { /* bufsize */
			if (!JS_ValueToInt32(cx, argv[i], &bufsize))
	rc = JS_SUSPENDREQUEST(cx);
	if (shareable)
		p->fp = fopen(p->name, p->mode);
		if ((file = nopen(p->name, fopenflags(p->mode))) != -1) {
			char *fdomode = strdup(p->mode);
			char *e = fdomode;
			if (fdomode && e) {
				/* Remove deprecated (never-worked, non-standard) 'e'xclusive mode char (and warn): */
				for (e = strchr(fdomode, 'e'); e ; e = strchr(e, 'e')) {
					JS_ReportWarning(cx, "Deprecated file open mode: 'e'");
					memmove(e, e + 1, strlen(e));
				}
				/* Remove (C11 standard) 'x'clusive mode char to avoid MSVC assertion: */
				for (e = strchr(fdomode, 'x'); e ; e = strchr(e, 'x'))
					memmove(e, e + 1, strlen(e));
				if ((p->fp = fdopen(file, fdomode)) == NULL) {
					JS_ReportWarning(cx, "fdopen(%s, %s) ERROR %d: %s", p->name, fdomode, errno, strerror(errno));
				}
	if (p->fp != NULL) {
		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
		dbprintf(FALSE, p, "opened: %s", p->name);
		if (!bufsize)
			setvbuf(p->fp, NULL, _IONBF, 0); /* no buffering */
			setvbuf(p->fp, NULL, _IOFBF, bufsize);
	} else if (file >= 0)
js_popen(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	uintN      i;
	jsint      bufsize = 2 * 1024;
	JSString*  str;
	private_t* p;
	jsrefcount rc;
	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {

	SAFECOPY(p->mode, "r+"); /* default mode */
	for (i = 0; i < argc; i++) {
		if (JSVAL_IS_STRING(argv[i])) {  /* mode */
			if ((str = JS_ValueToString(cx, argv[i])) == NULL)
				return JS_FALSE;
			JSSTRING_TO_STRBUF(cx, str, p->mode, sizeof(p->mode), NULL);
		else if (JSVAL_IS_NUMBER(argv[i])) { /* bufsize */
			if (!JS_ValueToInt32(cx, argv[i], &bufsize))
	rc = JS_SUSPENDREQUEST(cx);
	p->fp = popen(p->name, p->mode);
	if (p->fp != NULL) {
		p->pipe = TRUE;
		JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
		dbprintf(FALSE, p, "popened: %s", p->name);
		if (!bufsize)
			setvbuf(p->fp, NULL, _IONBF, 0); /* no buffering */
			setvbuf(p->fp, NULL, _IOFBF, bufsize);
js_close(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	private_t* p;
	jsrefcount rc;
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	rc = JS_SUSPENDREQUEST(cx);
	dbprintf(FALSE, p, "closed: %s", p->name);

static JSBool
js_raw_pollin(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	private_t* p;
	jsrefcount rc;
	int32      timeout = -1;
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	if (argc && !JSVAL_NULL_OR_VOID(argv[0])) {
		if (!JS_ValueToInt32(cx, argv[0], &timeout))
	}

	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(FALSE));
	rc = JS_SUSPENDREQUEST(cx);
Deucе's avatar
Deucе committed
	/*
	 * TODO: macOS poll() page has the ominous statement that "The poll() system call currently does not support devices."
	 *       But, since we don't support OS X in Synchronet that's likely OK?
	 */
	if (socket_readable(fileno(p->fp), timeout))
		JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(TRUE));
#else
		if (isatty(fileno(p->fp))) {
			if (kbhit()) {
				JS_RESUMEREQUEST(cx, rc);
				JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(TRUE));
				rc = JS_SUSPENDREQUEST(cx);
				break;
			}
			SLEEP(1);
			if (timeout > 0)
				timeout--;
		}
		else {
			if (!eof(fileno(p->fp))) {
				JS_RESUMEREQUEST(cx, rc);
				JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(TRUE));
				rc = JS_SUSPENDREQUEST(cx);
				break;
			}
			SLEEP(1);
			if (timeout > 0)
				timeout--;
		}
	}
#endif
	JS_RESUMEREQUEST(cx, rc);
	return JS_TRUE;
}

static JSBool
js_raw_read(JSContext *cx, uintN argc, jsval *arglist)
{
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	char*      buf;
	int32      len;
	JSString*  str;
	private_t* p;
	jsrefcount rc;
	int        fd;
	off_t      pos;
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {

	if (argc && !JSVAL_NULL_OR_VOID(argv[0])) {
		if (!JS_ValueToInt32(cx, argv[0], &len))
	if ((buf = malloc(len)) == NULL)
	rc = JS_SUSPENDREQUEST(cx);
	// https://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_05.html#tag_02_05_01
	/* For the first handle, the first applicable condition below applies. After the actions
	 * required below are taken, if the handle is still open, the application can close it.
	 *   If it is a file descriptor, no action is required.
	 *   If the only further action to be performed on any handle to this open file descriptor
	     *      is to close it, no action need be taken.
	 *   If it is a stream which is unbuffered, no action need be taken.
	 *   If it is a stream which is line buffered, and the last byte written to the stream was a
	 *      <newline> (that is, as if a: putc('\n') was the most recent operation on that stream),
	 *      no action need be taken.
	 *   If it is a stream which is open for writing or appending (but not also open for reading),
	 *      the application shall either perform an fflush(), or the stream shall be closed.
	 *   If the stream is open for reading and it is at the end of the file ( feof() is true), no
	 *      action need be taken.
	 *   If the stream is open with a mode that allows reading and the underlying open file
	 *      description refers to a device that is capable of seeking, the application shall
	 *      either perform an fflush(), or the stream shall be closed.
	 * Otherwise, the result is undefined.
	 * For the second handle:
	 *   If any previous active handle has been used by a function that explicitly changed the file
	 *      offset, except as required above for the first handle, the application shall perform an
	 *      lseek() or fseek() (as appropriate to the type of handle) to an appropriate location.
	 */
	/*
	 * Since we don't want to overcomplicate this, it basically boils down to:
	 * Call fflush() on the stream, lseek() on the descriptor, diddle the descriptor, then fseek() the
	 * stream.
	 *
	 * The only option bit is the fflush() on the stream, but it never hurts and is sometimes
	 * required by POSIX.
	 */
	fflush(p->fp);
	pos = ftello(p->fp);
		len = 0;
	else {
		fd = fileno(p->fp);
		lseek(fd, pos, SEEK_SET);
		len = read(fileno(p->fp), buf, len);
		fseeko(p->fp, pos + (len >= 0 ? len : 0), SEEK_SET);
		dbprintf(FALSE, p, "read %d raw bytes", len);
		if (len < 0)
			len = 0;
	JS_RESUMEREQUEST(cx, rc);

	str = JS_NewStringCopyN(cx, buf, len);
	free(buf);


	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));

js_read(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	char*      cp;
	char*      buf;
	char*      uubuf;
	int32      len;
	int32      offset;
	int32      uulen;
	JSString*  str;
	private_t* p;
	jsrefcount rc;
	JS_SET_RVAL(cx, arglist, JSVAL_NULL);
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	if (argc && !JSVAL_NULL_OR_VOID(argv[0])) {
		if (!JS_ValueToInt32(cx, argv[0], &len))
		rc = JS_SUSPENDREQUEST(cx);
		len = (long)filelength(fileno(p->fp));
		offset = (long)ftell(p->fp);
		if (offset > 0)
			len -= offset;
	if ((buf = malloc(len + 1)) == NULL)
	rc = JS_SUSPENDREQUEST(cx);
	len = fread(buf, 1, len, p->fp);
	dbprintf(FALSE, p, "read %u bytes", len);
	if (len < 0)
		len = 0;
	buf[len] = 0;
	if (p->etx) {
		cp = strchr(buf, p->etx);
	if (p->uuencoded || p->b64encoded || p->yencoded) {
		uulen = len * 2;
		if ((uubuf = malloc(uulen)) == NULL) {
		if (p->uuencoded)
			uulen = uuencode(uubuf, uulen, buf, len);
		else if (p->yencoded)
			uulen = yencode(uubuf, uulen, buf, len);
			uulen = b64_encode(uubuf, uulen, buf, len);
		if (uulen >= 0) {
			buf = uubuf;
			len = uulen;
deuce's avatar
deuce committed
		}
	str = JS_NewStringCopyN(cx, buf, len);
	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(str));
js_readln(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	char*      cp;
	char*      buf;
	int32      len = 512;
	JSString*  js_str;
	private_t* p;
	jsrefcount rc;
	JS_SET_RVAL(cx, arglist, JSVAL_NULL);
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	if (argc && !JSVAL_NULL_OR_VOID(argv[0])) {
		if (!JS_ValueToInt32(cx, argv[0], &len))
	if ((buf = malloc(len + 1)) == NULL)
	rc = JS_SUSPENDREQUEST(cx);
	if (fgets(buf, len + 1, p->fp) != NULL) {
		len = strlen(buf);
		while (len > 0 && (buf[len - 1] == '\r' || buf[len - 1] == '\n'))
		buf[len] = 0;
		if (p->etx) {
			cp = strchr(buf, p->etx);
		if ((js_str = JS_NewStringCopyZ(cx, buf)) != NULL)    /* exception here Feb-12-2005 */
			JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str));
deuce's avatar
deuce committed
	free(buf);
js_readbin(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	BYTE *     b;
	WORD *     w;
	DWORD *    l;
	uint64_t * q;
	int32      size = sizeof(DWORD);
	private_t* p;
	int32      count = 1;
	size_t     retlen;
	void *     buffer = NULL;
	size_t     i;
	JSObject*  array;
	jsval      v;
	jsrefcount rc;
	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(-1));
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	if (argc && !JSVAL_NULL_OR_VOID(argv[0])) {
		if (!JS_ValueToInt32(cx, argv[0], &size))
		if (argc > 1 && !JSVAL_NULL_OR_VOID(argv[1])) {
			if (!JS_ValueToInt32(cx, argv[1], &count))
	rc = JS_SUSPENDREQUEST(cx);
	if (size != sizeof(BYTE) && size != sizeof(WORD) && size != sizeof(DWORD) && size != sizeof(uint64_t)) {
		dbprintf(TRUE, p, "unsupported binary read size: %d", size);
	buffer = malloc(size * count);
	if (buffer == NULL) {
		dbprintf(TRUE, p, "malloc failure of %u bytes", size * count);
	b = buffer;
	w = buffer;
	l = buffer;
	q = buffer;
	retlen = fread(buffer, size, count, p->fp);
	if (count == 1) {
		if (retlen == 1) {
			switch (size) {
					JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(*b));
					if (p->network_byte_order)
						*w = BE_SHORT(*w);
					else
						*w = LE_SHORT(*w);
					JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(*w));
					break;
				case sizeof(DWORD):
					if (p->network_byte_order)
						*l = BE_LONG(*l);
					else
						*l = LE_LONG(*l);
					JS_SET_RVAL(cx, arglist, UINT_TO_JSVAL(*l));
					if (p->network_byte_order)
						*q = BE_INT64(*q);
					else
						*q = LE_INT64(*q);
					JS_SET_RVAL(cx, arglist, DOUBLE_TO_JSVAL((double)*q));
		array = JS_NewArrayObject(cx, 0, NULL);
		for (i = 0; i < retlen; i++) {
			switch (size) {
				case sizeof(BYTE):
					v = INT_TO_JSVAL(*(b++));
					break;
				case sizeof(WORD):
					if (p->network_byte_order)
						*w = BE_SHORT(*w);
					else
						*w = LE_SHORT(*w);
					v = INT_TO_JSVAL(*(w++));
					break;
				case sizeof(DWORD):
					if (p->network_byte_order)
						*l = BE_LONG(*l);
					else
						*l = LE_LONG(*l);
					v = UINT_TO_JSVAL(*(l++));
					if (p->network_byte_order)
						*q = BE_INT64(*q);
					else
						*q = LE_INT64(*q);
					v = DOUBLE_TO_JSVAL((double)*(q++));
			if (!JS_SetElement(cx, array, i, &v)) {
				rc = JS_SUSPENDREQUEST(cx);
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array));
js_readall(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsint      len = 0;
	JSObject*  array;
	private_t* p;
	JS_SET_RVAL(cx, arglist, JSVAL_NULL);
	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	array = JS_NewArrayObject(cx, 0, NULL);
	while (!feof(p->fp)) {
		if (JS_RVAL(cx, arglist) == JSVAL_NULL)
		if (!JS_SetElement(cx, array, len++, &JS_RVAL(cx, arglist)))
	JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array));
static jsval get_value(JSContext *cx, char* value, bool blanks)
	char* p;
	BOOL  f = FALSE;
	jsval val;
	if (value == NULL || (*value == 0 && !blanks))
	for (p = value; *p; p++) {
		if (*p == '.' && !f)
			f = TRUE;
		else if (!IS_DIGIT(*p))
	if (p != value && *p == 0) {
		if (f)
			val = DOUBLE_TO_JSVAL(atof(value));
			val = UINT_TO_JSVAL(strtoul(value, NULL, 10));
	if (!strncmp(value, "0x", 2)) {
		for (p = value + 2; *p; p++)
			if (!isxdigit((uchar) * p))
		if (*p == 0) {
			val = UINT_TO_JSVAL(strtoul(value, NULL, 0));
	if (!stricmp(value, "true"))
	if (!stricmp(value, "false"))
	return STRING_TO_JSVAL(JS_NewStringCopyZ(cx, value));
deuce's avatar
deuce committed
static double js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed

	if (!JS_CallFunctionName(cx, obj, "getTime", 0, NULL, &rval)) {
		return ((double)time(NULL)) * 1000;
deuce's avatar
deuce committed
	}
deuce's avatar
deuce committed
	return JSVAL_TO_DOUBLE(rval);
}

js_iniGetValue(JSContext *cx, uintN argc, jsval *arglist)
	JSObject * obj = JS_THIS_OBJECT(cx, arglist);
	jsval *    argv = JS_ARGV(cx, arglist);
	char*      section = ROOT_SECTION;
	char*      key;
	char**     list;
	char       buf[INI_MAX_VALUE_LEN];
	int32      i;
	jsval      val;
	jsval      dflt = argv[2];
	private_t* p;
	JSObject*  array;
	JSObject*  dflt_obj;
	JSObject*  date_obj;
	jsrefcount rc;
	double     dbl;
	time_t     tt;
	char*      cstr = NULL;
	char*      cstr2;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	if (argc && argv[0] != JSVAL_VOID && argv[0] != JSVAL_NULL)
		JSVALUE_TO_MSTRING(cx, argv[0], section, NULL);
	JSVALUE_TO_MSTRING(cx, argv[1], key, NULL);
	if (JS_IsExceptionPending(cx)) {
		FREE_AND_NULL(section);
		FREE_AND_NULL(key);
deuce's avatar
deuce committed
		return JS_FALSE;
	/*
	 * Although section can be NULL (ie: root), a NULL key will cause a
	 * segfault.
	 */
		JS_ReportError(cx, "Invalid NULL key specified");
	str_list_t ini = iniReadFile(p->fp);
	if (argc < 3 || dflt == JSVAL_VOID) {  /* unspecified default value */
		rc = JS_SUSPENDREQUEST(cx);
		cstr = iniGetString(ini, section, key, NULL, buf);
		FREE_AND_NULL(section);
		FREE_AND_NULL(key);
		strListFree(&ini);
		JS_SET_RVAL(cx, arglist, get_value(cx, cstr, /* blanks */ false));
	if (JSVAL_IS_BOOLEAN(dflt)) {
		JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(
						iniGetBool(ini, section, key, JSVAL_TO_BOOLEAN(dflt))));
deuce's avatar
deuce committed
	}
	else if (JSVAL_IS_OBJECT(dflt)) {
		if ((dflt_obj = JSVAL_TO_OBJECT(dflt)) != NULL && (strcmp("Date", JS_GetClass(cx, dflt_obj)->name) == 0)) {
			tt = (time_t)(js_DateGetMsecSinceEpoch(cx, dflt_obj) / 1000.0);
			rc = JS_SUSPENDREQUEST(cx);
			dbl = (double)iniGetDateTime(ini, section, key, tt);
deuce's avatar
deuce committed
			dbl *= 1000;
deuce's avatar
deuce committed
			date_obj = JS_NewDateObjectMsec(cx, dbl);
			if (date_obj != NULL) {
deuce's avatar
deuce committed
				JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(date_obj));
deuce's avatar
deuce committed
			}
deuce's avatar
deuce committed
		}
		else {
			array = JS_NewArrayObject(cx, 0, NULL);
			cstr = NULL;
			JSVALUE_TO_MSTRING(cx, dflt, cstr, NULL);
			if (JS_IsExceptionPending(cx)) {
				FREE_AND_NULL(cstr);
				FREE_AND_NULL(section);
				FREE_AND_NULL(key);
				strListFree(&ini);
			rc = JS_SUSPENDREQUEST(cx);
			list = iniGetStringList(ini, section, key, ",", cstr);
			for (i = 0; list && list[i]; i++) {
				val = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, list[i]));
				if (!JS_SetElement(cx, array, i, &val))
			rc = JS_SUSPENDREQUEST(cx);
			JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(array));
deuce's avatar
deuce committed
		}
	}
	else if (JSVAL_IS_DOUBLE(dflt)) {
		rc = JS_SUSPENDREQUEST(cx);
		dbl = iniGetFloat(ini, section, key, JSVAL_TO_DOUBLE(dflt));
deuce's avatar
deuce committed
		JS_RESUMEREQUEST(cx, rc);
		JS_SET_RVAL(cx, arglist, DOUBLE_TO_JSVAL(dbl));
deuce's avatar
deuce committed
	}
	else if (JSVAL_IS_NUMBER(dflt)) {
		if (!JS_ValueToInt32(cx, dflt, &i)) {
			FREE_AND_NULL(section);
			FREE_AND_NULL(key);
			strListFree(&ini);
		rc = JS_SUSPENDREQUEST(cx);
		i = iniGetInteger(ini, section, key, i);
deuce's avatar
deuce committed
		JS_RESUMEREQUEST(cx, rc);
		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(i));
deuce's avatar
deuce committed
	} else {
		JSVALUE_TO_MSTRING(cx, dflt, cstr, NULL);
		if (JS_IsExceptionPending(cx)) {
			FREE_AND_NULL(cstr);
			FREE_AND_NULL(section);
			FREE_AND_NULL(key);
			strListFree(&ini);
		rc = JS_SUSPENDREQUEST(cx);
		cstr2 = iniGetString(ini, section, key, cstr, buf);
deuce's avatar
deuce committed
		JS_RESUMEREQUEST(cx, rc);
		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, cstr2)));
		FREE_AND_NULL(cstr);
	FREE_AND_NULL(section);
	FREE_AND_NULL(key);
	strListFree(&ini);
js_iniSetValue_internal(JSContext *cx, JSObject *obj, uintN argc, jsval* argv, str_list_t* list)
	char*      section = ROOT_SECTION;
	char*      key = NULL;
	char*      result = NULL;
	int32      i;
	jsval      value = argv[2];
	private_t* p;
	JSObject*  value_obj;
	jsrefcount rc;
	char*      cstr;
	time_t     tt;

	if ((p = (private_t*)js_GetClassPrivate(cx, obj, &js_file_class)) == NULL) {
	if (argc && argv[0] != JSVAL_VOID && argv[0] != JSVAL_NULL)
		JSVALUE_TO_MSTRING(cx, argv[0], section, NULL);
	JSVALUE_TO_MSTRING(cx, argv[1], key, NULL);
	if (JS_IsExceptionPending(cx)) {
		FREE_AND_NULL(section);
		FREE_AND_NULL(key);
	if (value == JSVAL_VOID) {     /* unspecified value */
		rc = JS_SUSPENDREQUEST(cx);
		result = iniSetString(list, section, key, "", &p->ini_style);
	else if (JSVAL_IS_BOOLEAN(value)) {
		result = iniSetBool(list, section, key, JSVAL_TO_BOOLEAN(value), &p->ini_style);
deuce's avatar
deuce committed
	}
	else if (JSVAL_IS_DOUBLE(value)) {
		result = iniSetFloat(list, section, key, JSVAL_TO_DOUBLE(value), &p->ini_style);
deuce's avatar
deuce committed
	}
	else if (JSVAL_IS_NUMBER(value)) {
		if (!JS_ValueToInt32(cx, value, &i)) {
			FREE_AND_NULL(section);
			FREE_AND_NULL(key);
		rc = JS_SUSPENDREQUEST(cx);
		result = iniSetInteger(list, section, key, i, &p->ini_style);
deuce's avatar
deuce committed
		JS_RESUMEREQUEST(cx, rc);
	} else if (JSVAL_IS_OBJECT(value)
	           && (value_obj = JSVAL_TO_OBJECT(value)) != NULL
	           && (strcmp("Date", JS_GetClass(cx, value_obj)->name) == 0)) {
		tt = (time_t)(js_DateGetMsecSinceEpoch(cx, value_obj) / 1000.0);
		rc = JS_SUSPENDREQUEST(cx);
		result = iniSetDateTime(list, section, key, /* include_time */ TRUE, tt, &p->ini_style);
deuce's avatar
deuce committed
		JS_RESUMEREQUEST(cx, rc);
	} else {
		JSVALUE_TO_MSTRING(cx, value, cstr, NULL);
		if (JS_IsExceptionPending(cx)) {
			FREE_AND_NULL(cstr);
			FREE_AND_NULL(section);
			FREE_AND_NULL(key);
		rc = JS_SUSPENDREQUEST(cx);
		result = iniSetString(list, section, key, cstr, &p->ini_style);
deuce's avatar
deuce committed
		JS_RESUMEREQUEST(cx, rc);