diff --git a/src/sbbs3/js_file_area.c b/src/sbbs3/js_file_area.c
index 7b0bec7a63e47a4016f44dcb7139dceb48db0d07..86c5796751171a9a573b9d9a87f7abbd8f25ae80 100644
--- a/src/sbbs3/js_file_area.c
+++ b/src/sbbs3/js_file_area.c
@@ -1,9 +1,3 @@
-/* js_file_area.c */
-
-/* Synchronet JavaScript "File Area" Object */
-
-/* $Id: js_file_area.c,v 1.56 2020/08/16 01:01:09 rswindell Exp $ */
-
 /****************************************************************************
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
@@ -17,21 +11,9 @@
  * 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.	*
  ****************************************************************************/
 
@@ -86,6 +68,7 @@ static char* dir_prop_desc[] = {
 	,"percent of file size awarded uploader in credits upon file upload"
 	,"percent of file size awarded uploader in credits upon subsequent downloads"
 	,"directory link (for HTML index)"
+	,"number of files currently in this directory <i>(introduced in v3.18c)</i>"
 	,"user has sufficient access to view this directory (e.g. list files) <i>(introduced in v3.18)</i>"
 	,"user has sufficient access to upload files to this directory"
 	,"user has sufficient access to download files from this directory"
@@ -103,6 +86,92 @@ struct js_file_area_priv {
 	user_t		*user;
 	client_t	*client;
 	char		*html_index_file;
+	uint		dirnum;
+};
+
+static void 
+js_file_area_finalize(JSContext *cx, JSObject *obj)
+{
+	struct js_file_area_priv *p;
+
+	if((p=(struct js_file_area_priv*)JS_GetPrivate(cx,obj))==NULL)
+		return;
+
+	free(p->html_index_file);
+	free(p);
+	JS_SetPrivate(cx,obj,NULL);
+}
+
+/***************************************/
+/* Dynamic Directory Object Properties */
+/***************************************/
+enum {
+	 DIR_PROP_FILES
+	,DIR_PROP_CAN_ACCESS
+	,DIR_PROP_CAN_UPLOAD
+	,DIR_PROP_CAN_DOWNLOAD
+	,DIR_PROP_IS_EXEMPT
+	,DIR_PROP_IS_OPERATOR
+};
+
+static struct JSPropertySpec js_dir_properties[] = {
+/*		 name				,tinyid		,flags	*/
+
+	{	"files"			,DIR_PROP_FILES			,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY },
+	{	"can_access"	,DIR_PROP_CAN_ACCESS	,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY },
+	{	"can_upload"	,DIR_PROP_CAN_UPLOAD	,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY },
+	{	"can_download"	,DIR_PROP_CAN_DOWNLOAD	,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY },
+	{	"is_exempt"		,DIR_PROP_IS_EXEMPT		,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY },
+	{	"is_operator"	,DIR_PROP_IS_OPERATOR	,JSPROP_ENUMERATE|JSPROP_SHARED|JSPROP_READONLY },
+	{0}
+};
+
+static JSBool js_dir_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
+{
+	jsval idval;
+    jsint       tiny;
+	struct js_file_area_priv *p;
+
+	if((p=(struct js_file_area_priv*)JS_GetPrivate(cx, obj))==NULL)
+		return JS_FALSE;
+
+    JS_IdToValue(cx, id, &idval);
+    tiny = JSVAL_TO_INT(idval);
+
+	switch(tiny) {
+		case DIR_PROP_FILES:
+			*vp = UINT_TO_JSVAL(getfiles(p->cfg, p->dirnum));
+			break;
+		case DIR_PROP_CAN_ACCESS:
+			*vp = BOOLEAN_TO_JSVAL(p->user == NULL || can_user_access_dir(p->cfg, p->dirnum, p->user, p->client));
+			break;
+		case DIR_PROP_CAN_UPLOAD:
+			*vp = BOOLEAN_TO_JSVAL(p->user == NULL || can_user_upload(p->cfg, p->dirnum, p->user, p->client, /* reason: */NULL));
+			break;
+		case DIR_PROP_CAN_DOWNLOAD:
+			*vp = BOOLEAN_TO_JSVAL(p->user == NULL || can_user_download(p->cfg, p->dirnum, p->user, p->client, /* reason: */NULL));
+			break;
+		case DIR_PROP_IS_EXEMPT:
+			*vp = BOOLEAN_TO_JSVAL(p->user == NULL || is_download_free(p->cfg, p->dirnum, p->user, p->client));
+			break;
+		case DIR_PROP_IS_OPERATOR:
+			*vp = BOOLEAN_TO_JSVAL(p->user == NULL || is_user_dirop(p->cfg, p->dirnum, p->user, p->client));
+			break;
+	}
+	return JS_TRUE;
+}
+
+static JSClass js_dir_class = {
+     "FileDir"				/* name			*/
+    ,JSCLASS_HAS_PRIVATE	/* flags		*/
+	,JS_PropertyStub		/* addProperty	*/
+	,JS_PropertyStub		/* delProperty	*/
+	,js_dir_get				/* getProperty	*/
+	,JS_StrictPropertyStub	/* setProperty	*/
+	,JS_EnumerateStub		/* enumerate	*/
+	,JS_ResolveStub			/* resolve		*/
+	,JS_ConvertStub			/* convert		*/
+	,js_file_area_finalize	/* finalize		*/
 };
 
 JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id)
@@ -120,7 +189,6 @@ JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id)
 	jsint		lib_index;
 	jsint		dir_index;
 	uint		l,d;
-	BOOL		is_op;
 	char*		name=NULL;
 	struct js_file_area_priv *p;
 
@@ -284,12 +352,19 @@ JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id)
 				if(p->cfg->dir[d]->lib!=l)
 					continue;
 
-				if((dirobj=JS_NewObject(cx, NULL, NULL, NULL))==NULL)
+				if((dirobj=JS_NewObject(cx, &js_dir_class, NULL, NULL))==NULL)
 					return JS_FALSE;
+				struct js_file_area_priv *np = malloc(sizeof(struct js_file_area_priv));
+				if(np == NULL)
+					continue;
+				*np = *p;
+				np->dirnum = d;
+				np->html_index_file = NULL;
+				JS_SetPrivate(cx, dirobj, np);
 
 				val=OBJECT_TO_JSVAL(dirobj);
 				dir_index=-1;
-				if(p->user==NULL || chk_ar(p->cfg,p->cfg->dir[d]->ar,p->user,p->client)) {
+				if(p->user==NULL || can_user_access_dir(p->cfg, d, p->user, p->client)) {
 
 					if(!JS_GetArrayLength(cx, dir_list, (jsuint*)&dir_index))
 						return JS_FALSE;
@@ -454,11 +529,8 @@ JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id)
 				val=STRING_TO_JSVAL(js_str);
 				if(!JS_SetProperty(cx, dirobj, "link", &val))
 					return JS_FALSE;
-
-				if(p->user!=NULL 
-					&& (p->user->level>=SYSOP_LEVEL 
-						|| (p->cfg->dir[d]->op_ar!=NULL && p->cfg->dir[d]->op_ar[0]!=0 
-							&& chk_ar(p->cfg,p->cfg->dir[d]->op_ar,p->user,p->client))))
+#if 0
+				if(p->user == NULL || is_user_dirop(p->cfg, d, p->user, p->client))
 					is_op=TRUE;
 				else
 					is_op=FALSE;
@@ -467,19 +539,14 @@ JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id)
 				if(!JS_SetProperty(cx, dirobj, "can_access", &val))
 					return JS_FALSE;
 
-				if(p->user==NULL 
-					|| ((is_op || p->user->exempt&FLAG('U')
-					|| chk_ar(p->cfg,p->cfg->dir[d]->ul_ar,p->user,p->client))
-					&& !(p->user->rest&FLAG('T')) && !(p->user->rest&FLAG('U'))))
+				if(p->user == NULL || can_user_upload(p->cfg, d, p->user, p->client, /* reason: */NULL))
 					val=JSVAL_TRUE;
 				else
 					val=JSVAL_FALSE;
 				if(!JS_SetProperty(cx, dirobj, "can_upload", &val))
 					return JS_FALSE;
 
-				if(p->user==NULL 
-					|| (chk_ar(p->cfg,p->cfg->dir[d]->dl_ar,p->user,p->client)
-					&& !(p->user->rest&FLAG('T')) && !(p->user->rest&FLAG('D'))))
+				if(p->user == NULL || can_user_download(p->cfg, d, p->user, p->client, /* reason: */NULL))
 					val=JSVAL_TRUE;
 				else
 					val=JSVAL_FALSE;
@@ -499,6 +566,9 @@ JSBool DLLCALL js_file_area_resolve(JSContext* cx, JSObject* areaobj, jsid id)
 					val=JSVAL_FALSE;
 				if(!JS_SetProperty(cx, dirobj, "is_operator", &val))
 					return JS_FALSE;
+#endif
+				if(!JS_DefineProperties(cx, dirobj, js_dir_properties))
+					return JS_FALSE;
 
 				val=BOOLEAN_TO_JSVAL(d==p->cfg->lib[l]->offline_dir);
 				if(!JS_SetProperty(cx, dirobj, "is_offline", &val))
@@ -541,20 +611,6 @@ static JSBool js_file_area_enumerate(JSContext *cx, JSObject *obj)
 	return(js_file_area_resolve(cx, obj, JSID_VOID));
 }
 
-static void 
-js_file_area_finalize(JSContext *cx, JSObject *obj)
-{
-	struct js_file_area_priv *p;
-
-	if((p=(struct js_file_area_priv*)JS_GetPrivate(cx,obj))==NULL)
-		return;
-
-	free(p->html_index_file);
-	free(p);
-	JS_SetPrivate(cx,obj,NULL);
-}
-
-
 static JSClass js_file_area_class = {
      "FileArea"				/* name			*/
     ,JSCLASS_HAS_PRIVATE	/* flags		*/