From 95191fa9043c0152f8cf23d6bb509b4579aef53d Mon Sep 17 00:00:00 2001
From: "Rob Swindell (on Windows 11)" <rob@synchro.net>
Date: Sun, 5 Jan 2025 21:03:30 -0800
Subject: [PATCH] New @-codes: FILE_FTP_PATH and FILE_WEB_PATH (don't include
 scheme and host)

These @-codes can be used to construct ftp[s] and http[s] URLs to display to
users. For use in new/optional display file text/menu/download.*

Add optional configurable 'vpath' (per-directory) for directories that have
web/ftp aliases, so they preferred/short path (alias) will be used in the
expanded @-codes.

In SCFG, display each directory's virtual file path ([auto-generated] or set
manually).

getfilevpath() no longer assumes the target buf is >= MAX_PATH+1 bytes long

Add dir_vpath() to get a directory's vpath

Extend maximum file library parent directory from 47 to 100 chars. This limit
was likely imposed because we didn't have horiztonal scrolling input in UIFC
getstr() support at the time. We're no longer limited by that.
---
 src/sbbs3/atcodes.cpp     |  15 ++++++
 src/sbbs3/filedat.c       |   7 +--
 src/sbbs3/filedat.h       |   2 +-
 src/sbbs3/js_filebase.c   |   2 +-
 src/sbbs3/scfg/scfgxfr2.c | 101 ++++++++++++++++++++++++--------------
 src/sbbs3/scfgdefs.h      |   3 +-
 src/sbbs3/scfglib.h       |   1 +
 src/sbbs3/scfglib1.c      |  13 +++++
 src/sbbs3/scfglib2.c      |   1 +
 src/sbbs3/scfgsave.c      |   1 +
 10 files changed, 103 insertions(+), 43 deletions(-)

diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index 915eed0a4a..46ba93021a 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -26,6 +26,7 @@
 #include "cp437defs.h"
 #include "ver.h"
 #include "petdefs.h"
+#include "filedat.h"
 
 #if defined(_WINSOCKAPI_)
 	extern WSADATA WSAData;
@@ -2268,6 +2269,20 @@ const char* sbbs_t::atcode(const char* sp, char* str, size_t maxlen, int* pmode,
 				safe_snprintf(str, maxlen, "%u", getusrdir(current_file->dir));
 				return str;
 			}
+			if(strcmp(sp, "FILE_FTP_PATH") == 0) {
+				getfilevpath(&cfg, current_file, str, maxlen);
+				return str;
+			}
+			if(strcmp(sp, "FILE_WEB_PATH") == 0) {
+				char* p = "";
+				if(cfg.dir[current_file->dir]->vpath[0] == '\0') {
+					p = startup->web_file_vpath_prefix;
+					if(*p == '/')
+						++p;
+				}
+				safe_snprintf(str, maxlen, "%s%s", p, getfilevpath(&cfg, current_file, tmp, sizeof tmp));
+				return str;
+			}
 		}
 		if(strcmp(sp, "FILE_NAME") == 0)
 			return current_file->name;
diff --git a/src/sbbs3/filedat.c b/src/sbbs3/filedat.c
index 83051b759a..c345f3a0b9 100644
--- a/src/sbbs3/filedat.c
+++ b/src/sbbs3/filedat.c
@@ -634,13 +634,14 @@ char* getfilepath(scfg_t* cfg, file_t* f, char* path)
 	return path;
 }
 
-char* getfilevpath(scfg_t* cfg, file_t* f, char* path)
+char* getfilevpath(scfg_t* cfg, file_t* f, char* path, size_t size)
 {
+	char vpath[LEN_DIR + 1];
 	const char* name = f->name == NULL ? f->file_idx.name : f->name;
 	if(!is_valid_dirnum(cfg, f->dir))
 		return "";
-	safe_snprintf(path, MAX_PATH, "%s/%s/%s"
-		,cfg->lib[cfg->dir[f->dir]->lib]->vdir, cfg->dir[f->dir]->vdir, name);
+	safe_snprintf(path, size, "%s/%s"
+		,dir_vpath(cfg, cfg->dir[f->dir], vpath, sizeof vpath), name);
 	return path;
 }
 
diff --git a/src/sbbs3/filedat.h b/src/sbbs3/filedat.h
index 66cd1761b0..e129ec3574 100644
--- a/src/sbbs3/filedat.h
+++ b/src/sbbs3/filedat.h
@@ -47,7 +47,7 @@ DLLEXPORT str_list_t	loadfilenames(smb_t*, const char* filespec, time_t t, enum
 DLLEXPORT void			sortfilenames(str_list_t, size_t count, enum file_sort);
 DLLEXPORT bool			updatefile(scfg_t*, file_t*);
 DLLEXPORT char*			getfilepath(scfg_t*, file_t*, char* path);
-DLLEXPORT char*			getfilevpath(scfg_t*, file_t*, char* path);
+DLLEXPORT char*			getfilevpath(scfg_t*, file_t*, char* path, size_t);
 DLLEXPORT off_t			getfilesize(scfg_t*, file_t*);
 DLLEXPORT time_t		getfiletime(scfg_t*, file_t*);
 DLLEXPORT ulong			gettimetodl(scfg_t*, file_t*, uint rate_cps);
diff --git a/src/sbbs3/js_filebase.c b/src/sbbs3/js_filebase.c
index d8d9e1b5d9..a45bd6a14f 100644
--- a/src/sbbs3/js_filebase.c
+++ b/src/sbbs3/js_filebase.c
@@ -181,7 +181,7 @@ set_file_properties(JSContext *cx, JSObject* obj, file_t* f, enum file_detail de
 		|| !JS_DefineProperty(cx, obj, "name", STRING_TO_JSVAL(js_str), NULL, NULL, flags))
 		return false;
 
-	if((js_str = JS_NewStringCopyZ(cx, getfilevpath(scfg, f, path))) == NULL
+	if((js_str = JS_NewStringCopyZ(cx, getfilevpath(scfg, f, path, sizeof path))) == NULL
 		|| !JS_DefineProperty(cx, obj, "vpath", STRING_TO_JSVAL(js_str), NULL, NULL, flags | JSPROP_READONLY))
 	return false;
 
diff --git a/src/sbbs3/scfg/scfgxfr2.c b/src/sbbs3/scfg/scfgxfr2.c
index f09df723cb..c347647325 100644
--- a/src/sbbs3/scfg/scfgxfr2.c
+++ b/src/sbbs3/scfg/scfgxfr2.c
@@ -1764,8 +1764,8 @@ void dir_cfg(int libnum)
 	char* dir_transfer_path_help =
 		"`Transfer File Path:`\n"
 		"\n"
-		"This is the default storage path for files uploaded-to and available for\n"
-		"download-from this directory.\n"
+		"This is the physical storage path for files uploaded-to and available\n"
+		"for download-from this directory.\n"
 		"\n"
 		"If this setting is blank, the internal-code (lower-cased) is used as the\n"
 		"default directory name.\n"
@@ -1936,6 +1936,13 @@ void dir_cfg(int libnum)
 			snprintf(opt[n++], MAX_OPLN, "%-27.27s%s%s","Internal Code"
 				,cfg.lib[cfg.dir[i]->lib]->code_prefix, cfg.dir[i]->code_suffix);
 			snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","FidoNet Area Tag",area_tag);
+			if(cfg.dir[i]->vpath[0] != '\0')
+				SAFECOPY(str, cfg.dir[i]->vpath);
+			else {
+				init_vdir(&cfg, cfg.dir[i]);
+				SAFEPRINTF(str, "[%s]", dir_vpath(&cfg, cfg.dir[i], path, sizeof path));
+			}
+			snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Virtual File Path", str);
 			snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Access Requirements"
 				,cfg.dir[i]->arstr);
 			snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Upload Requirements"
@@ -1964,8 +1971,7 @@ void dir_cfg(int libnum)
 				SAFECOPY(str, path);
 			else
 				SAFEPRINTF(str, "[%s]", path);
-			snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Transfer File Path"
-				,str);
+			snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Transfer File Path", str);
 			if(cfg.dir[i]->maxfiles)
 				sprintf(str, "%u", cfg.dir[i]->maxfiles);
 			else
@@ -2061,83 +2067,104 @@ void dir_cfg(int libnum)
 						SAFECOPY(cfg.dir[i]->area_tag, str);
 					break;
 				case 4:
+					uifc.helpbuf=
+						"`Virtual File Path:`\n"
+						"\n"
+						"This is the path, without leading or trailing slashes, where the files\n"
+						"in this directory will appear for users of the Synchronet FTP and Web\n"
+						"servers.\n"
+						"\n"
+						"If no path is specified here, then one is automatically generated and\n"
+						"displayed here within `[`brackets`]` for your information.\n"
+						"\n"
+						"Automatically generated virtual paths use the `Virtual Sub-directories`\n"
+						"naming convention as selected for the parent File Library.\n"
+						"\n"
+						"Setting this path implies that the sysop has `also` set corresponding\n"
+						"FTP and Web Server directory aliases (i.e. in their `ftpalias.cfg` and\n"
+						"`web_alias.ini` files).\n"
+						;
+					uifc.input(WIN_L2R|WIN_SAV,0,17,"Virtual File Path"
+						,cfg.dir[i]->vpath, sizeof(cfg.dir[i]->vpath) - 1, K_EDIT | K_NOSPACE);
+					break;
+				case 5:
 					sprintf(str,"%s Access",cfg.dir[i]->sname);
 					getar(str,cfg.dir[i]->arstr);
 					break;
-				case 5:
+				case 6:
 					sprintf(str,"%s Upload",cfg.dir[i]->sname);
 					getar(str,cfg.dir[i]->ul_arstr);
 					break;
-				case 6:
+				case 7:
 					sprintf(str,"%s Download",cfg.dir[i]->sname);
 					getar(str,cfg.dir[i]->dl_arstr);
 					break;
-				case 7:
+				case 8:
 					sprintf(str,"%s Operator",cfg.dir[i]->sname);
 					getar(str,cfg.dir[i]->op_arstr);
 					break;
-				case 8:
+				case 9:
 					sprintf(str,"%s Exemption",cfg.dir[i]->sname);
 					getar(str,cfg.dir[i]->ex_arstr);
 					break;
-				case 9:
+				case 10:
 					uifc.helpbuf = dir_transfer_path_help;
 					uifc.input(WIN_L2R|WIN_SAV,0,17,"Transfer File Path"
 						,cfg.dir[i]->path,sizeof(cfg.dir[i]->path)-1,K_EDIT);
 					break;
-				case 10:
+				case 11:
 					uifc.helpbuf = max_files_help;
 					sprintf(str,"%u",cfg.dir[i]->maxfiles);
 					uifc.input(WIN_L2R|WIN_SAV,0,17,"Maximum Number of Files (0=Unlimited)"
 						,str,5,K_EDIT|K_NUMBER);
 					cfg.dir[i]->maxfiles=atoi(str);
 					break;
-				case 11:
+				case 12:
 					sprintf(str,"%u",cfg.dir[i]->maxage);
 					uifc.helpbuf = max_age_help;
 					uifc.input(WIN_MID|WIN_SAV,0,17,"Maximum Age of Files (in days)"
 						,str,5,K_EDIT|K_NUMBER);
 					cfg.dir[i]->maxage=atoi(str);
 					break;
-				case 12:
+				case 13:
 					uifc.helpbuf = up_pct_help;
 					uifc.input(WIN_MID|WIN_SAV,0,0
 						,"Percentage of Credits to Credit Uploader on Upload"
 						,ultoa(cfg.dir[i]->up_pct,tmp,10),4,K_EDIT|K_NUMBER);
 					cfg.dir[i]->up_pct=atoi(tmp);
 					break;
-				case 13:
+				case 14:
 					uifc.helpbuf = dn_pct_help;
 					uifc.input(WIN_MID|WIN_SAV,0,0
 						,"Percentage of Credits to Credit Uploader on Download"
 						,ultoa(cfg.dir[i]->dn_pct,tmp,10),4,K_EDIT|K_NUMBER);
 					cfg.dir[i]->dn_pct=atoi(tmp);
 					break;
-				case 14:
+				case 15:
 					dir_toggle_options(cfg.dir[i]);
 					break;
-			case 15:
-				while(1) {
-					n=0;
-					snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Extensions Allowed"
-						,cfg.dir[i]->exts);
-					if(!cfg.dir[i]->data_dir[0])
-						sprintf(str,"[%sdirs/]",cfg.data_dir);
-					else
-						strcpy(str,cfg.dir[i]->data_dir);
-					snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Data Directory"
-						,str);
-					snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Upload Semaphore File"
-						,cfg.dir[i]->upload_sem);
-					snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Sort Value and Direction"
-						,file_sort_desc[cfg.dir[i]->sort]);
-					snprintf(opt[n++], MAX_OPLN, "%-27.27sNow %u / Was %u","Directory Index", i, cfg.dir[i]->dirnum);
-					opt[n][0]=0;
-					uifc.helpbuf=
-						"`Directory Advanced Options:`\n"
-						"\n"
-						"This is the advanced options menu for the selected file directory.\n"
-					;
+				case 16:
+					while(1) {
+						n=0;
+						snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Extensions Allowed"
+							,cfg.dir[i]->exts);
+						if(!cfg.dir[i]->data_dir[0])
+							sprintf(str,"[%sdirs/]",cfg.data_dir);
+						else
+							strcpy(str,cfg.dir[i]->data_dir);
+						snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Data Directory"
+							,str);
+						snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Upload Semaphore File"
+							,cfg.dir[i]->upload_sem);
+						snprintf(opt[n++], MAX_OPLN, "%-27.27s%s","Sort Value and Direction"
+							,file_sort_desc[cfg.dir[i]->sort]);
+						snprintf(opt[n++], MAX_OPLN, "%-27.27sNow %u / Was %u","Directory Index", i, cfg.dir[i]->dirnum);
+						opt[n][0]=0;
+						uifc.helpbuf=
+							"`Directory Advanced Options:`\n"
+							"\n"
+							"This is the advanced options menu for the selected file directory.\n"
+							;
 						n=uifc.list(WIN_ACT|WIN_SAV|WIN_RHT|WIN_BOT,3,4,66,&adv_dflt,0
 							,"Advanced Options",opt);
 						if(n==-1)
@@ -2174,7 +2201,7 @@ void dir_cfg(int libnum)
 								break;
 						}
 					}
-				break;
+					break;
 			}
 		}
 	}
diff --git a/src/sbbs3/scfgdefs.h b/src/sbbs3/scfgdefs.h
index c4df44680a..b220d56142 100644
--- a/src/sbbs3/scfgdefs.h
+++ b/src/sbbs3/scfgdefs.h
@@ -74,6 +74,7 @@ typedef struct {							/* Transfer Directory Info */
 	char		code[LEN_EXTCODE+1];		/* Internal code (with optional lib prefix) */
 	char		code_suffix[LEN_CODE+1];	/* Eight character code suffix */
 	char		vdir[LEN_SLNAME+1];			/* Virtual Directory name */
+	char		vpath[LEN_DIR+1];			/* Optional vpath (e.g. alias) */
 	char		area_tag[FIDO_AREATAG_LEN+1];
 	char		lname[LEN_SLNAME+1],		/* Long name - used for listing */
 				sname[LEN_SSNAME+1],		/* Short name - used for prompts */
@@ -113,7 +114,7 @@ typedef struct {							/* Transfer Library Information */
 				ex_arstr[LEN_ARSTR+1], 		/* Exemption Requirements (credits) */
 				op_arstr[LEN_ARSTR+1], 		/* Operator Requirements */
 				code_prefix[LEN_CODE+1],	/* Prefix for internal code */
-				parent_path[48];			/* Parent for dir paths */
+				parent_path[LEN_DIR+1];		/* Parent for dir paths */
 	uchar		ar[LEN_ARSTR+1],
 				ul_ar[LEN_ARSTR+1],
 				dl_ar[LEN_ARSTR+1],
diff --git a/src/sbbs3/scfglib.h b/src/sbbs3/scfglib.h
index 51f02132f8..25dab3dba3 100644
--- a/src/sbbs3/scfglib.h
+++ b/src/sbbs3/scfglib.h
@@ -81,6 +81,7 @@ DLLEXPORT bool	is_valid_xtrnsec(scfg_t*, int);
 DLLEXPORT char *	sub_newsgroup_name(scfg_t*, sub_t*, char*, size_t);
 DLLEXPORT char *	sub_area_tag(scfg_t*, sub_t*, char*, size_t);
 DLLEXPORT char *	dir_area_tag(scfg_t*, dir_t*, char*, size_t);
+DLLEXPORT char *	dir_vpath(scfg_t*, dir_t* dir, char* path, size_t);
 
 uint nearest_sysfaddr_index(scfg_t*, faddr_t*);
 faddr_t* nearest_sysfaddr(scfg_t*, faddr_t*);
diff --git a/src/sbbs3/scfglib1.c b/src/sbbs3/scfglib1.c
index 1e2e4bf25f..981865d0ce 100644
--- a/src/sbbs3/scfglib1.c
+++ b/src/sbbs3/scfglib1.c
@@ -976,3 +976,16 @@ char* dir_area_tag(scfg_t* cfg, dir_t* dir, char* str, size_t size)
 	strupr(str);
 	return str;
 }
+
+/****************************************************************************/
+/* Returns virtual path for a file directory, without trailing slash		*/
+/****************************************************************************/
+char* dir_vpath(scfg_t* cfg, dir_t* dir, char* path, size_t size)
+{
+	if(dir->vpath[0] != '\0')
+		return dir->vpath;
+	else
+		safe_snprintf(path, size, "%s/%s"
+			,cfg->lib[dir->lib]->vdir, dir->vdir);
+	return path;
+}
diff --git a/src/sbbs3/scfglib2.c b/src/sbbs3/scfglib2.c
index 018ce12f2c..ca6ac5ab88 100644
--- a/src/sbbs3/scfglib2.c
+++ b/src/sbbs3/scfglib2.c
@@ -308,6 +308,7 @@ bool read_file_cfg(scfg_t* cfg, char* error, size_t maxerrlen)
 			cfg->lib[cfg->dir[i]->lib]->offline_dir=i;
 
 		init_vdir(cfg, cfg->dir[i]);
+		SAFECOPY(cfg->dir[i]->vpath, iniGetString(section, NULL, "vpath", "", value));
 
 		SAFECOPY(cfg->dir[i]->arstr, iniGetString(section, NULL, "ars", "", value));
 		SAFECOPY(cfg->dir[i]->ul_arstr, iniGetString(section, NULL, "upload_ars", "", value));
diff --git a/src/sbbs3/scfgsave.c b/src/sbbs3/scfgsave.c
index f1d6861fc9..a765101527 100644
--- a/src/sbbs3/scfgsave.c
+++ b/src/sbbs3/scfgsave.c
@@ -784,6 +784,7 @@ bool write_file_cfg(scfg_t* cfg)
 				iniSetString(&section, name, "area_tag", cfg->dir[i]->area_tag, &ini_style);
 				backslash(cfg->dir[i]->path);
 				iniSetString(&section, name, "path", cfg->dir[i]->path, &ini_style);
+				iniSetString(&section, name, "vpath", cfg->dir[i]->vpath, &ini_style);
 
 				if (cfg->dir[i]->misc&DIR_FCHK) {
 					SAFECOPY(path, cfg->dir[i]->path);
-- 
GitLab