Skip to content
Snippets Groups Projects
js_file.c 38.4 KiB
Newer Older

/* File Object Properites */
enum {
	 FILE_PROP_NAME		
	,FILE_PROP_MODE
	,FILE_PROP_ETX
	,FILE_PROP_EXISTS	
	,FILE_PROP_DATE		
	,FILE_PROP_IS_OPEN	
	,FILE_PROP_EOF		
	,FILE_PROP_ERROR	
	,FILE_PROP_DESCRIPTOR
	,FILE_PROP_DEBUG	
	,FILE_PROP_POSITION	
	,FILE_PROP_LENGTH	
	,FILE_PROP_ATTRIBUTES
	/* dynamically calculated */
	,FILE_PROP_CHKSUM
	,FILE_PROP_CRC16
	,FILE_PROP_CRC32
	,FILE_PROP_MD5_HEX
static JSBool js_file_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    jsint       tiny;
	private_t*	p;

	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		JS_ReportError(cx,getprivate_failure,WHERE);

    tiny = JSVAL_TO_INT(id);

	dbprintf(FALSE, p, "setting property %d",tiny);

	switch(tiny) {
		case FILE_PROP_DEBUG:
			JS_ValueToBoolean(cx,*vp,&(p->debug));
		case FILE_PROP_YENCODED:
			JS_ValueToBoolean(cx,*vp,&(p->yencoded));
			break;
			JS_ValueToBoolean(cx,*vp,&(p->uuencoded));
			break;
		case FILE_PROP_B64ENCODED:
			JS_ValueToBoolean(cx,*vp,&(p->b64encoded));
			break;
		case FILE_PROP_ROT13:
			JS_ValueToBoolean(cx,*vp,&(p->rot13));
			break;
		case FILE_PROP_NETWORK_ORDER:
			JS_ValueToBoolean(cx,*vp,&(p->network_byte_order));
		case FILE_PROP_POSITION:
			if(p->fp!=NULL)
				fseek(p->fp,JSVAL_TO_INT(*vp),SEEK_SET);
			break;
		case FILE_PROP_LENGTH:
			if(p->fp!=NULL)
				chsize(fileno(p->fp),JSVAL_TO_INT(*vp));
			break;
		case FILE_PROP_ATTRIBUTES:
			CHMOD(p->name,JSVAL_TO_INT(*vp));
			break;
		case FILE_PROP_ETX:
			p->etx = (uchar)JSVAL_TO_INT(*vp);
			break;
	}

	return(JS_TRUE);
}

static JSBool js_file_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
	BYTE*		buf;
	long		l;
	long		len;
	long		offset;
	ulong		sum;
	BYTE		digest[MD5_DIGEST_SIZE];
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL) {
		JS_ReportError(cx,getprivate_failure,WHERE);

    tiny = JSVAL_TO_INT(id);

#if 0 /* just too much */
	dbprintf(FALSE, sock, "getting property %d",tiny);
#endif

	switch(tiny) {
		case FILE_PROP_NAME:
			if((js_str=JS_NewStringCopyZ(cx, p->name))==NULL)
				return(JS_FALSE);
			*vp = STRING_TO_JSVAL(js_str);
			break;
		case FILE_PROP_MODE:
			if((js_str=JS_NewStringCopyZ(cx, p->mode))==NULL)
				return(JS_FALSE);
			*vp = STRING_TO_JSVAL(js_str);
			break;
		case FILE_PROP_EXISTS:
			if(p->fp)	/* open? */
			else
				*vp = BOOLEAN_TO_JSVAL(fexist(p->name));
			break;
		case FILE_PROP_DATE:
			JS_NewNumberValue(cx,fdate(p->name),vp);
			break;
		case FILE_PROP_IS_OPEN:
			*vp = BOOLEAN_TO_JSVAL(p->fp!=NULL);
			break;
		case FILE_PROP_EOF:
			if(p->fp)
				*vp = BOOLEAN_TO_JSVAL(feof(p->fp)!=0);
			else
			break;
		case FILE_PROP_ERROR:
			if(p->fp)
				*vp = INT_TO_JSVAL(ferror(p->fp));
			else
			break;
		case FILE_PROP_POSITION:
				JS_NewNumberValue(cx,ftell(p->fp),vp);
			break;
		case FILE_PROP_LENGTH:
			if(p->fp)	/* open? */
				JS_NewNumberValue(cx,filelength(fileno(p->fp)),vp);
				JS_NewNumberValue(cx,flength(p->name),vp);
			break;
		case FILE_PROP_ATTRIBUTES:
			JS_NewNumberValue(cx,getfattr(p->name),vp);
			break;
		case FILE_PROP_DEBUG:
			*vp = BOOLEAN_TO_JSVAL(p->debug);
			break;
		case FILE_PROP_YENCODED:
			*vp = BOOLEAN_TO_JSVAL(p->yencoded);
			break;
		case FILE_PROP_UUENCODED:
			*vp = BOOLEAN_TO_JSVAL(p->uuencoded);
		case FILE_PROP_B64ENCODED:
			*vp = BOOLEAN_TO_JSVAL(p->b64encoded);
			break;
		case FILE_PROP_ROT13:
			*vp = BOOLEAN_TO_JSVAL(p->rot13);
			break;
		case FILE_PROP_NETWORK_ORDER:
			*vp = BOOLEAN_TO_JSVAL(p->network_byte_order);
			break;
		case FILE_PROP_DESCRIPTOR:
			if(p->fp)
				*vp = INT_TO_JSVAL(fileno(p->fp));
			else
				*vp = INT_TO_JSVAL(-1);
		case FILE_PROP_ETX:
			*vp = INT_TO_JSVAL(p->etx);
			break;
		case FILE_PROP_CHKSUM:
		case FILE_PROP_CRC16:
		case FILE_PROP_CRC32:
			*vp = JSVAL_ZERO;
			if(p->fp==NULL)
				break;
			/* fall-through */
			*vp = JSVAL_VOID;
			if(p->fp==NULL)
				break;
			offset=ftell(p->fp);			/* save current file position */
			fseek(p->fp,0,SEEK_SET);
			len=filelength(fileno(p->fp));
			if(len<1)
				break;
			if((buf=malloc(len*2))==NULL)
				break;
			len=fread(buf,sizeof(BYTE),len,p->fp);
			if(len>0) 
				switch(tiny) {
					case FILE_PROP_CHKSUM:
						for(sum=l=0;l<len;l++)
							sum+=buf[l];
						JS_NewNumberValue(cx,sum,vp);
						break;
					case FILE_PROP_CRC16:
						if(!JS_NewNumberValue(cx,crc16(buf,len),vp))
							*vp=JSVAL_ZERO;
						sum=crc32(buf,len);
						if(!JS_NewNumberValue(cx,sum,vp))
							*vp=JSVAL_ZERO;
						break;
					case FILE_PROP_MD5_HEX:
						MD5_calc(digest,buf,len);
						MD5_hex(str,digest);
						js_str=JS_NewStringCopyZ(cx, str);
						break;
					case FILE_PROP_MD5_B64:
						MD5_calc(digest,buf,len);
						b64_encode(str,sizeof(str)-1,digest,sizeof(digest));
						js_str=JS_NewStringCopyZ(cx, str);
			free(buf);
			fseek(p->fp,offset,SEEK_SET);	/* restore saved file position */
			if(js_str!=NULL)
				*vp = STRING_TO_JSVAL(js_str);
			break;
	return(JS_TRUE);
}

#define FILE_PROP_FLAGS JSPROP_ENUMERATE|JSPROP_READONLY

static struct JSPropertySpec js_file_properties[] = {
/*		 name				,tinyid					,flags,				getter,	setter	*/
	{	"name"				,FILE_PROP_NAME			,FILE_PROP_FLAGS,	NULL,NULL},
	{	"mode"				,FILE_PROP_MODE			,FILE_PROP_FLAGS,	NULL,NULL},
	{	"exists"			,FILE_PROP_EXISTS		,FILE_PROP_FLAGS,	NULL,NULL},
	{	"date"				,FILE_PROP_DATE			,FILE_PROP_FLAGS,	NULL,NULL},
	{	"is_open"			,FILE_PROP_IS_OPEN		,FILE_PROP_FLAGS,	NULL,NULL},
	{	"eof"				,FILE_PROP_EOF			,FILE_PROP_FLAGS,	NULL,NULL},
	{	"error"				,FILE_PROP_ERROR		,FILE_PROP_FLAGS,	NULL,NULL},
	{	"descriptor"		,FILE_PROP_DESCRIPTOR	,FILE_PROP_FLAGS,	NULL,NULL},
	/* writeable */
	{	"etx"				,FILE_PROP_ETX			,JSPROP_ENUMERATE,  NULL,NULL},
	{	"debug"				,FILE_PROP_DEBUG		,JSPROP_ENUMERATE,	NULL,NULL},
	{	"position"			,FILE_PROP_POSITION		,JSPROP_ENUMERATE,	NULL,NULL},
	{	"length"			,FILE_PROP_LENGTH		,JSPROP_ENUMERATE,	NULL,NULL},
	{	"attributes"		,FILE_PROP_ATTRIBUTES	,JSPROP_ENUMERATE,	NULL,NULL},
	{	"network_byte_order",FILE_PROP_NETWORK_ORDER,JSPROP_ENUMERATE,	NULL,NULL},
	{	"rot13"				,FILE_PROP_ROT13		,JSPROP_ENUMERATE,	NULL,NULL},
	{	"uue"				,FILE_PROP_UUENCODED	,JSPROP_ENUMERATE,	NULL,NULL},
	{	"yenc"				,FILE_PROP_YENCODED		,JSPROP_ENUMERATE,	NULL,NULL},
	{	"base64"			,FILE_PROP_B64ENCODED	,JSPROP_ENUMERATE,	NULL,NULL},
	/* dynamically calculated */
	{	"crc16"				,FILE_PROP_CRC16		,FILE_PROP_FLAGS,	NULL,NULL},
	{	"crc32"				,FILE_PROP_CRC32		,FILE_PROP_FLAGS,	NULL,NULL},
	{	"chksum"			,FILE_PROP_CHKSUM		,FILE_PROP_FLAGS,	NULL,NULL},
	{	"md5_hex"			,FILE_PROP_MD5_HEX		,FILE_PROP_FLAGS,	NULL,NULL},
	{	"md5_base64"		,FILE_PROP_MD5_B64		,FILE_PROP_FLAGS,	NULL,NULL},
#ifdef _DEBUG
static char* file_prop_desc[] = {
	 "filename specified in constructor - <small>READ ONLY</small>"
	,"mode string specified in <i>open</i> call - <small>READ ONLY</small>"
	,"<i>true</i> if the file exists - <small>READ ONLY</small>"
	,"last modified date/time (time_t format) - <small>READ ONLY</small>"
	,"<i>true</i> if the file has been opened successfully - <small>READ ONLY</small>"
	,"<i>true</i> if the current file position is at the <i>end of file</i> - <small>READ ONLY</small>"
	,"the last occurred error value (use clear_error to clear) - <small>READ ONLY</small>"
	,"the open file descriptor (advanced use only) - <small>READ ONLY</small>"
	,"end-of-text character (advanced use only), if non-zero used by <i>read</i>, <i>readln</i>, and <i>write</i>"
rswindell's avatar
rswindell committed
	,"set to <i>true</i> to enable debug log output"
	,"the current file position (offset in bytes), change value to seek within file"
	,"the current length of the file (in bytes)"
	,"file mode/attributes"
	,"set to <i>true</i> if binary data is to be written and read in Network Byte Order (big end first)"
	,"set to <i>true</i> to enable automatic ROT13 translatation of text"
	,"set to <i>true</i> to enable automatic Unix-to-Unix encode and decode on <tt>read</tt> and <tt>write</tt> calls"
	,"set to <i>true</i> to enable automatic yEnc encode and decode on <tt>read</tt> and <tt>write</tt> calls"
	,"set to <i>true</i> to enable automatic Base64 encode and decode on <tt>read</tt> and <tt>write</tt> calls"
	,"calculated 16-bit CRC of file contents - <small>READ ONLY</small>"
	,"calculated 32-bit CRC of file contents - <small>READ ONLY</small>"
	,"calculated 32-bit checksum of file contents - <small>READ ONLY</small>"
	,"calculated 128-bit MD5 digest of file contents as hexadecimal string - <small>READ ONLY</small>"
	,"calculated 128-bit MD5 digest of file contents as base64-encoded string - <small>READ ONLY</small>"
static jsMethodSpec js_file_functions[] = {
	{"open",			js_open,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("[string mode, boolean shareable, number buflen]")
rswindell's avatar
rswindell committed
	,JSDOCSTR("open file, <i>shareable</i> defaults to <i>false</i>, <i>buflen</i> defaults to 2048 bytes, "
		"mode (default: <tt>w+</tt>) specifies the type of access requested for the file, as follows:<br>"
		"<tt>r&nbsp</tt> open for reading; if the file does not exist or cannot be found, the open call fails<br>"
		"<tt>w&nbsp</tt> open an empty file for writing; if the given file exists, its contents are destroyed<br>"
		"<tt>a&nbsp</tt> open for writing at the end of the file (appending); creates the file first if it doesnt exist<br>"
		"<tt>r+</tt> open for both reading and writing (the file must exist)<br>"
		"<tt>w+</tt> open an empty file for both reading and writing; if the given file exists, its contents are destroyed<br>"
		"<tt>a+</tt> open for reading and appending<br>"
		"<tt>b&nbsp</tt> open in binary (untranslated) mode; translations involving carriage-return and linefeed characters are suppressed (e.g. <tt>r+b</tt>)<br>"
	},		
	{"close",			js_close,			0,	JSTYPE_VOID,	""
	,JSDOCSTR("close file")
	{"remove",			js_delete,			0,	JSTYPE_BOOLEAN, ""
	,JSDOCSTR("remove the file from the disk")
	{"clearError",		js_clear_error,		0,	JSTYPE_ALIAS },
	{"clear_error",		js_clear_error,		0,	JSTYPE_BOOLEAN, ""
	,JSDOCSTR("clears the current error value (AKA clearError)")
	},
	{"flush",			js_flush,			0,	JSTYPE_BOOLEAN,	""
	,JSDOCSTR("flush/commit buffers to disk")
	{"lock",			js_lock,			2,	JSTYPE_BOOLEAN,	JSDOCSTR("[offset, length]")
	,JSDOCSTR("lock file record for exclusive access (file must be opened <i>shareable</i>)")
	{"unlock",			js_unlock,			2,	JSTYPE_BOOLEAN,	JSDOCSTR("[offset, length]")
	,JSDOCSTR("unlock file record for exclusive access")
	{"read",			js_read,			0,	JSTYPE_STRING,	JSDOCSTR("[maxlen]")
	,JSDOCSTR("read a string from file (optionally unix-to-unix or base64 decoding in the process), "
		"<i>maxlen</i> defaults to the current length of the file minus the current file position")
	{"readln",			js_readln,			0,	JSTYPE_STRING,	JSDOCSTR("[maxlen]")
	,JSDOCSTR("read a line-feed terminated string, <i>maxlen</i> defaults to 512 characters")
	{"readBin",			js_readbin,			0,	JSTYPE_NUMBER,	JSDOCSTR("[bytes]")
	,JSDOCSTR("read a binary integer from the file, default number of <i>bytes</i> is 4 (32-bits)")
	},
	{"readAll",			js_readall,			0,	JSTYPE_ARRAY,	""
	,JSDOCSTR("read all lines into an array of strings")
	{"write",			js_write,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("string text [,len]")
	,JSDOCSTR("write a string to the file (optionally unix-to-unix or base64 decoding in the process)")
	{"writeln",			js_writeln,			0,	JSTYPE_BOOLEAN, JSDOCSTR("[string text]")
	,JSDOCSTR("write a line-feed terminated string to the file")
	{"writeBin",		js_writebin,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("value [,bytes]")
	,JSDOCSTR("write a binary integer to the file, default number of <i>bytes</i> is 4 (32-bits)")
	{"writeAll",		js_writeall,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("array lines")
	,JSDOCSTR("write an array of strings to file")
	{"printf",			js_fprintf,			0,	JSTYPE_NUMBER,	JSDOCSTR("string format [,args]")
rswindell's avatar
rswindell committed
	,JSDOCSTR("write a formatted string to the file (ala fprintf) - "
		"<small>CAUTION: for experienced C programmers ONLY</small>")
	},
	{"iniGetSections",	js_iniGetSections,	0,	JSTYPE_ARRAY,	JSDOCSTR("[prefix]")
rswindell's avatar
rswindell committed
	,JSDOCSTR("parse all section names from a <tt>.ini</tt> file (format = '<tt>[section]</tt>') "
		"and return the section names as an <i>array of strings</i>, "
		"optionally, only those section names that begin with the specified <i>prefix</i>")
rswindell's avatar
rswindell committed
	},
	{"iniGetKeys",		js_iniGetKeys,		0,	JSTYPE_ARRAY,	JSDOCSTR("section")
	,JSDOCSTR("parse all key names from the specified <i>section</i> in a <tt>.ini</tt> file "
		"and return the key names as an <i>array of strings</i>")
	},
	{"iniGetValue",		js_iniGetValue,		3,	JSTYPE_STRING,	JSDOCSTR("section, key, default")
	,JSDOCSTR("parse a key from a <tt>.ini</tt> file and return its value (format = '<tt>key = value</tt>'). "
		"returns the specified <i>default</i> value if the key or value is missing or invalid. "
		"will return a <i>bool</i>, <i>number</i>, <i>string</i>, or an <i>array of strings</i> "
		"determined by the type of <i>default</i> value specified")
	},
	{"iniGetObject",	js_iniGetObject,	1,	JSTYPE_OBJECT,	JSDOCSTR("section")
	,JSDOCSTR("parse an entire section from a .ini file "
		"and return all of its keys and values as properties of an object")
	},
	{"iniGetAllObjects",js_iniGetAllObjects,1,	JSTYPE_ARRAY,	JSDOCSTR("[name_property] [,prefix]")
	,JSDOCSTR("parse all sections from a .ini file and return all sections and keys "
		"an array of objects with each section's keys as properties of each section object, "
		"<i>name_property</i> is the name of the property to create to contain the section's name "
		"(default is <tt>\"name\"</tt>), "
		"the optional <i>prefix</i> has the same use as in the <tt>iniGetSections</tt> method, "
		"if a <i>prefix</i> is specified, it is removed from each section's name" )
/* File Destructor */

static void js_finalize_file(JSContext *cx, JSObject *obj)
{
	private_t* p;
	
	if((p=(private_t*)JS_GetPrivate(cx,obj))==NULL)
		return;

	if(p->external==JS_FALSE && p->fp!=NULL)
		fclose(p->fp);

	dbprintf(FALSE, p, "closed: %s",p->name);

	free(p);

	JS_SetPrivate(cx, obj, NULL);
}

static JSClass js_file_class = {
     "File"					/* name			*/
    ,JSCLASS_HAS_PRIVATE	/* flags		*/
	,JS_PropertyStub		/* addProperty	*/
	,JS_PropertyStub		/* delProperty	*/
	,js_file_get			/* getProperty	*/
	,js_file_set			/* setProperty	*/
	,JS_EnumerateStub		/* enumerate	*/
	,JS_ResolveStub			/* resolve		*/
	,JS_ConvertStub			/* convert		*/
	,js_finalize_file		/* finalize		*/
};

/* File Constructor (creates file descriptor) */

static JSBool
js_file_constructor(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	JSString*	str;
	private_t*	p;

	if((str = JS_ValueToString(cx, argv[0]))==NULL) {
		JS_ReportError(cx,"No filename specified");
		return(JS_FALSE);
	}

	*rval = JSVAL_VOID;

	if((p=(private_t*)calloc(1,sizeof(private_t)))==NULL) {
		JS_ReportError(cx,"calloc failed");
		return(JS_FALSE);
	}

	SAFECOPY(p->name,JS_GetStringBytes(str));

	if(!JS_SetPrivate(cx, obj, p)) {
		dbprintf(TRUE, p, "JS_SetPrivate failed");
		return(JS_FALSE);
	}

	if(!js_DefineMethods(cx, obj, js_file_functions, FALSE)) {
		dbprintf(TRUE, p, "js_DefineMethods failed");
		return(JS_FALSE);
	}

#ifdef _DEBUG
rswindell's avatar
rswindell committed
	js_DescribeObject(cx,obj,"Class used for opening, creating, reading, or writing files on the local file system<p>"
		"Special features include:</h2><ol type=disc>"
			"<li>Exclusive-access files (default) or shared files<ol type=circle>"
				"<li>optional record-locking"
				"<li>buffered or non-buffered I/O"
				"</ol>"
			"<li>Support for binary files<ol type=circle>"
				"<li>native or network byte order (endian)"
				"<li>automatic Unix-to-Unix (<i>UUE</i>), yEncode (<i>yEnc</i>) or Base64 encoding/decoding"
rswindell's avatar
rswindell committed
				"</ol>"
			"<li>Support for ASCII text files<ol type=circle>"
				"<li>supports line-based I/O<ol type=square>"
					"<li>entire file may be read or written as an array of strings"
					"<li>individual lines may be read or written one line at a time"
					"</ol>"
				"<li>supports fixed-length records<ol type=square>"
					"<li>optional end-of-text (<i>etx</i>) character for automatic record padding/termination"
					"<li>Synchronet <tt>.dat</tt> files use an <i>etx</i> value of 3 (Ctrl-C)"
					"</ol>"
				"<li>supports <tt>.ini</tt> formated configuration files"
				"<li>optional ROT13 encoding/translation"
				"</ol>"
			"<li>Dynamically-calculated industry standard checksums (e.g. CRC-16, CRC-32, MD5)"
			"</ol>"
			);
rswindell's avatar
rswindell committed
	js_DescribeConstructor(cx,obj,"To create a new File object: <tt>var f = new File(filename)</tt>");
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", file_prop_desc, JSPROP_READONLY);
#endif

	dbprintf(FALSE, p, "object constructed");
	return(JS_TRUE);
}

JSObject* DLLCALL js_CreateFileClass(JSContext* cx, JSObject* parent)
{
	JSObject*	sockobj;

	sockobj = JS_InitClass(cx, parent, NULL
		,&js_file_class
		,js_file_constructor
		,1		/* number of constructor args */
		,js_file_properties
		,NULL	/* funcs, set in constructor */
		,NULL,NULL);

	return(sockobj);
}

JSObject* DLLCALL js_CreateFileObject(JSContext* cx, JSObject* parent, char *name, FILE* fp)
{
	JSObject* obj;
	private_t*	p;

	obj = JS_DefineObject(cx, parent, name, &js_file_class, NULL
		,JSPROP_ENUMERATE|JSPROP_READONLY);

	if(obj==NULL)
		return(NULL);

	if(!JS_DefineProperties(cx, obj, js_file_properties))
		return(NULL);

	if (!js_DefineMethods(cx, obj, js_file_functions, FALSE)) 
		return(NULL);

	if((p=(private_t*)calloc(1,sizeof(private_t)))==NULL)
		return(NULL);

	p->fp=fp;
	p->debug=JS_FALSE;
	p->external=JS_TRUE;

	if(!JS_SetPrivate(cx, obj, p)) {
		dbprintf(TRUE, p, "JS_SetPrivate failed");
		return(NULL);
	}

	dbprintf(FALSE, p, "object created");

	return(obj);
}


#endif	/* JAVSCRIPT */