diff --git a/src/sbbs3/js_bbs.cpp b/src/sbbs3/js_bbs.cpp
index 59d5b8eb0527c81df89a97abe5b6db0e8995db75..a28b9ed1fadea950f368d2b972110992291c2287 100644
--- a/src/sbbs3/js_bbs.cpp
+++ b/src/sbbs3/js_bbs.cpp
@@ -1387,6 +1387,34 @@ js_user_event(JSContext *cx, uintN argc, jsval *arglist)
 	return(JS_TRUE);
 }
 
+static JSBool
+js_checkfname(JSContext *cx, uintN argc, jsval *arglist)
+{
+	jsval *argv = JS_ARGV(cx, arglist);
+	sbbs_t*		sbbs;
+	char*		fname = NULL;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+
+	if(argc < 1 || !JSVAL_IS_STRING(argv[0]))
+		return JS_TRUE;
+
+	if((sbbs = js_GetPrivate(cx, JS_THIS_OBJECT(cx, arglist))) == NULL)
+		return JS_FALSE;
+
+	JSVALUE_TO_MSTRING(cx, argv[0], fname, NULL);
+	if(fname == NULL)
+		return JS_FALSE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->checkfname(fname)));
+	JS_RESUMEREQUEST(cx, rc);
+	free(fname);
+
+	return JS_TRUE;
+}
+
 static JSBool
 js_chksyspass(JSContext *cx, uintN argc, jsval *arglist)
 {
@@ -4632,6 +4660,12 @@ static jsSyncMethodSpec js_bbs_functions[] = {
 	,316
 	},
 	/* security */
+	{"check_filename",	js_checkfname,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("filename")
+	,JSDOCSTR("verify that the specified <i>filename</i> string is legal and allowed for upload "
+		"(based on system configuration), returns <i>true</i> if the filename is allowed.<br>"
+		"Note: Will display <tt>text/badfile.msg</tt> for matching filenames, if it exists.")
+	,31902
+	},
 	{"check_syspass",	js_chksyspass,		0,	JSTYPE_BOOLEAN,	JSDOCSTR("[sys_pw]")
 	,JSDOCSTR("verify system password, prompting for the password if not passed as an argument")
 	,310
diff --git a/src/sbbs3/js_system.c b/src/sbbs3/js_system.c
index d25b40ee52e1ddce318b28c901551308efe75b36..f353f35152c019457078ce09c6aeb5104e88b5f5 100644
--- a/src/sbbs3/js_system.c
+++ b/src/sbbs3/js_system.c
@@ -1924,6 +1924,117 @@ js_chkname(JSContext *cx, uintN argc, jsval *arglist)
 
 	return(JS_TRUE);
 }
+
+static JSBool
+js_chkfname(JSContext *cx, uintN argc, jsval *arglist)
+{
+	JSObject *obj = JS_THIS_OBJECT(cx, arglist);
+	jsval *argv = JS_ARGV(cx, arglist);
+	char*		fname = NULL;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+
+	if(argc < 1 || !JSVAL_IS_STRING(argv[0]))
+		return JS_TRUE;
+
+	js_system_private_t* sys;
+	if((sys = (js_system_private_t*)js_GetClassPrivate(cx,obj,&js_system_class))==NULL)
+		return JS_FALSE;
+
+	JSVALUE_TO_MSTRING(cx, argv[0], fname, NULL);
+	if(fname == NULL)
+		return JS_FALSE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(!illegal_filename(fname)
+		&& allowed_filename(sys->cfg, fname)
+		&& !trashcan(sys->cfg, fname, "file")));
+	JS_RESUMEREQUEST(cx, rc);
+	free(fname);
+
+	return JS_TRUE;
+}
+
+static JSBool
+js_safest_fname(JSContext *cx, uintN argc, jsval *arglist)
+{
+	JSObject *obj = JS_THIS_OBJECT(cx, arglist);
+	jsval *argv = JS_ARGV(cx, arglist);
+	char*		fname = NULL;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+
+	if(argc < 1 || !JSVAL_IS_STRING(argv[0]))
+		return JS_TRUE;
+
+	JSVALUE_TO_MSTRING(cx, argv[0], fname, NULL);
+	if(fname == NULL)
+		return JS_FALSE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(safest_filename(fname)));
+	JS_RESUMEREQUEST(cx, rc);
+	free(fname);
+
+	return JS_TRUE;
+}
+
+static JSBool
+js_illegal_fname(JSContext *cx, uintN argc, jsval *arglist)
+{
+	JSObject *obj = JS_THIS_OBJECT(cx, arglist);
+	jsval *argv = JS_ARGV(cx, arglist);
+	char*		fname = NULL;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+
+	if(argc < 1 || !JSVAL_IS_STRING(argv[0]))
+		return JS_TRUE;
+
+	JSVALUE_TO_MSTRING(cx, argv[0], fname, NULL);
+	if(fname == NULL)
+		return JS_FALSE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(illegal_filename(fname)));
+	JS_RESUMEREQUEST(cx, rc);
+	free(fname);
+
+	return JS_TRUE;
+}
+
+static JSBool
+js_allowed_fname(JSContext *cx, uintN argc, jsval *arglist)
+{
+	JSObject *obj = JS_THIS_OBJECT(cx, arglist);
+	jsval *argv = JS_ARGV(cx, arglist);
+	char*		fname = NULL;
+	jsrefcount	rc;
+
+	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
+
+	if(argc < 1 || !JSVAL_IS_STRING(argv[0]))
+		return JS_TRUE;
+
+	js_system_private_t* sys;
+	if((sys = (js_system_private_t*)js_GetClassPrivate(cx,obj,&js_system_class))==NULL)
+		return JS_FALSE;
+
+	JSVALUE_TO_MSTRING(cx, argv[0], fname, NULL);
+	if(fname == NULL)
+		return JS_FALSE;
+
+	rc=JS_SUSPENDREQUEST(cx);
+	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(allowed_filename(sys->cfg, fname)));
+	JS_RESUMEREQUEST(cx, rc);
+	free(fname);
+
+	return JS_TRUE;
+}
+
 #endif
 
 static JSBool 
@@ -2128,6 +2239,25 @@ static jsSyncMethodSpec js_system_functions[] = {
 		"returns <i>true</i> if it is valid")
 	,315
 	},
+	{"check_filename",	js_chkfname,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("filename")
+	,JSDOCSTR("verify that the specified <i>filename</i> string is legal and allowed for upload by users "
+		"(based on system configuration), returns <i>true</i> if the filename is allowed")
+	,31902
+	},
+	{"allowed_filename", js_allowed_fname,	1,	JSTYPE_BOOLEAN,	JSDOCSTR("filename")
+	,JSDOCSTR("verify that the specified <i>filename</i> string is allowed for upload by users "
+		"(based on system configuration), returns <i>true</i> if the filename is allowed")
+	,31902
+	},
+	{"safest_filename",	js_safest_fname,	1,	JSTYPE_BOOLEAN,	JSDOCSTR("filename")
+	,JSDOCSTR("verify that the specified <i>filename</i> string contains only the safest subset of characters")
+	,31902
+	},
+	{"illegal_filename", js_illegal_fname,	1,	JSTYPE_BOOLEAN,	JSDOCSTR("filename")
+	,JSDOCSTR("check if the specified <i>filename</i> string contains illegal characters or sequences, "
+		"returns <i>true</i> if it is an illegal filename")
+	,31902
+	},
 #endif
 	{"check_pid",		js_chkpid,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("process-ID")
 	,JSDOCSTR("checks that the provided process ID is a valid executing process on the system, "