diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/AfterWork.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/AfterWork.ra new file mode 100644 index 00000000..1f5a4dc3 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/AfterWork.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/Daybreak.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/Daybreak.ra new file mode 100644 index 00000000..6f3ceb59 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/Daybreak.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/DialingWebTV.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/DialingWebTV.ra new file mode 100644 index 00000000..87dac586 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/DialingWebTV.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/Splash.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/Splash.ra new file mode 100644 index 00000000..f2b309db Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/Splash.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/WTVa.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/WTVa.ra new file mode 100644 index 00000000..fa299eca Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/WTVa.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/ghosttown.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/ghosttown.ra new file mode 100644 index 00000000..ef8c5065 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/ghosttown.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/karTV.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/karTV.ra new file mode 100644 index 00000000..8dccf1d8 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/karTV.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/majesty.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/majesty.ra new file mode 100644 index 00000000..dbcf06ef Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/majesty.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/mjstyWTV.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/mjstyWTV.ra new file mode 100644 index 00000000..a8795b6c Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/mjstyWTV.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/plingA.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/plingA.ra new file mode 100644 index 00000000..88d072c4 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/plingA.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/plingB.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/plingB.ra new file mode 100644 index 00000000..340100a1 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/plingB.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/seqJ.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/seqJ.ra new file mode 100644 index 00000000..c1cf32e8 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/seqJ.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/snowy1.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/snowy1.ra new file mode 100644 index 00000000..6f38925a Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/snowy1.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/snowy2.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/snowy2.ra new file mode 100644 index 00000000..05c3f0d3 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/snowy2.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/swelplng.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/swelplng.ra new file mode 100644 index 00000000..35f9742f Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/swelplng.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/tymplingA.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/tymplingA.ra new file mode 100644 index 00000000..aab8c77f Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/tymplingA.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/tymplingB.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/tymplingB.ra new file mode 100644 index 00000000..e3b88dd3 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/tymplingB.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/world1.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/world1.ra new file mode 100644 index 00000000..d27468a6 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/world1.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/world2.ra b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/world2.ra new file mode 100644 index 00000000..57ee555e Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/pnm/classicrom/world2.ra differ diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-music/ragen/catchall.js b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-music/ragen/catchall.js index be87d097..5be42660 100644 --- a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-music/ragen/catchall.js +++ b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-music/ragen/catchall.js @@ -16,13 +16,58 @@ if (minisrv_config.config.ServiceVaults) { throw ("ERROR: No Service Vaults defined!"); } +// Detect subdirectory structure of this catchall.js file and strip it from requests +// e.g., if at /ServiceVault/wtv-music/ragen/catchall.js, extract "ragen" +// if at /ServiceVault/wtv-music/ra/gen/catchall.js, extract "ra/gen" +let subDirPath = ''; +const currentDir = path.dirname(__filename); +const serviceVaultIdx = currentDir.indexOf('ServiceVault'); +console.log("DEBUG: currentDir =", currentDir, "serviceVaultIdx =", serviceVaultIdx); +if (serviceVaultIdx !== -1) { + const afterVault = currentDir.substring(serviceVaultIdx + 12); // 12 = length of 'ServiceVault' + console.log("DEBUG: afterVault =", afterVault); + const parts = afterVault.split(path.sep).filter(p => p); + console.log("DEBUG: parts =", parts); + if (parts.length > 1) { + // parts[0] is the service name (e.g., 'wtv-music'), parts[1+] are the subdirs + const subdirs = parts.slice(1); + subDirPath = '/' + subdirs.join('/'); + } +} +console.log("DEBUG: Detected subDirPath =", subDirPath); + const url_path = request_headers.request_url.split('?')[0]; const pathParts = url_path.split('/').filter(p => p); const serviceName = pathParts.length > 0 ? pathParts[0] : ''; -const remainingPath = '/' + pathParts.slice(1).join('/'); -const filename = remainingPath.split('/').pop().replace('.ram', ''); -const directory = remainingPath.endsWith('/') || !filename ? remainingPath.replace(/\/$/, '') : remainingPath.substring(0, remainingPath.lastIndexOf('/')); +let remainingPath = '/' + pathParts.slice(1).join('/'); +const hadTrailingSlash = request_headers.request_url.endsWith('/'); +console.log("DEBUG: Before stripping - subDirPath =", subDirPath, "remainingPath =", remainingPath); + +let strippedSubDir = ''; // Store what was stripped for link rebuilding +// Strip the subdirectory structure from the request path +if (subDirPath) { + if (remainingPath.startsWith(subDirPath + '/')) { + // Has something after the subdirectory, e.g., /ragen/classicrom + strippedSubDir = subDirPath; + remainingPath = remainingPath.substring(subDirPath.length); + } else if (remainingPath === subDirPath || remainingPath === subDirPath + '/') { + // Just the subdirectory itself, e.g., /ragen or /ragen/ + strippedSubDir = subDirPath; + remainingPath = '/'; + } +} + +console.log("DEBUG: After stripping - remainingPath =", remainingPath, "strippedSubDir =", strippedSubDir); + +// Restore trailing slash if original URL had one +if (hadTrailingSlash && !remainingPath.endsWith('/')) { + remainingPath += '/'; +} + +const filename = remainingPath.endsWith('/') ? '' : remainingPath.split('/').pop().replace('.ram', ''); +const directory = remainingPath.endsWith('/') ? remainingPath.replace(/\/$/, '') : remainingPath.substring(0, remainingPath.lastIndexOf('/')); +console.log("DEBUG: Request for service", serviceName, "with filename", filename, "and directory", directory, "remainingPath", remainingPath); let fileFound = false; const extensions = ['.ra', '.rm']; @@ -43,6 +88,8 @@ if (!filename || (request_headers.request_url.endsWith('/') && minisrv_config.se if (fs.statSync(fullPath).isFile() && (file.endsWith('.ra') || file.endsWith('.rm'))) { const baseFileName = file.substring(0, file.lastIndexOf('.')); allFiles.push(baseFileName + '.ram'); + } else if (fs.statSync(fullPath).isDirectory()) { + allFiles.push(file + '/'); } }); } @@ -51,7 +98,16 @@ if (!filename || (request_headers.request_url.endsWith('/') && minisrv_config.se if (allFiles.length > 0) { headers = `200 OK Content-type: text/html`; - data = `

RealAudio Files on this minisrv

`; + data = ` + + +RealAudio Files on this Service + + + +
+
+

RealAudio Files on this Service

`; } else { headers = `404 Not Found Content-type: text/html`; @@ -61,7 +117,7 @@ Content-type: text/html`; // Original file search logic for (const pnmVault of pnmVaults) { for (const ext of extensions) { - const filePath = path.join(pnmVault, filename + ext); + const filePath = path.join(pnmVault, directory, filename + ext); console.log("DEBUG: Checking for file", filePath); if (fs.existsSync(filePath)) { fileFound = true; @@ -76,8 +132,11 @@ Content-type: text/html`; headers = `404 Not Found Content-type: text/html`; } else { + const filePath = path.join(directory || '/', filename + path.extname(resolvedPath)); + const pnmURL = `pnm://${minisrv_config.config.service_ip}:${minisrv_config.services['pnm'].port}${filePath.replace(/\\/g, '/')}`; + console.log("DEBUG: File found at", resolvedPath, "serving as", pnmURL); headers = `200 OK Content-type: audio/x-pn-realaudio` - data = `pnm://${minisrv_config.config.service_ip}:${minisrv_config.services['pnm'].port}/${filename + path.extname(resolvedPath)}`; + data = pnmURL; } } \ No newline at end of file diff --git a/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/images/Realaudio_bg.gif b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/images/Realaudio_bg.gif new file mode 100644 index 00000000..267c9394 Binary files /dev/null and b/zefie_wtvp_minisrv/includes/ServiceVault/wtv-tricks/images/Realaudio_bg.gif differ diff --git a/zefie_wtvp_minisrv/includes/classes/WTVPNM.js b/zefie_wtvp_minisrv/includes/classes/WTVPNM.js index 9ca73121..618be3dc 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVPNM.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVPNM.js @@ -352,14 +352,35 @@ class WTVPNM { socket.write(headers + body, () => socket.end()); } + normalizeRequestedMediaPath(value) { + if (value === null || value === undefined) return null; + + let raw = String(value).replace(/\x00+$/g, '').trim(); + if (!raw) return null; + + // Trim query/fragment and normalize separators to URL-style slashes. + raw = raw.split(/[?#]/)[0].replace(/\\+/g, '/'); + + // Drop common URI scheme prefixes if present. + raw = raw.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/]*\/?/, ''); + raw = raw.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/*/, ''); + + // Keep only a safe relative path under the service vault. + const parts = raw.split('/').filter((part) => part && part !== '.' && part !== '..'); + if (parts.length === 0) return null; + + return parts.join('/'); + } + getRequestedMediaName(fields, rawData) { if (!Array.isArray(fields) || fields.length === 0) return this.scanRawForMediaName(rawData); // Field 0x52 (82) carries the requested file name in observed captures. const fileField = fields.find((f) => f && f.id === 82 && f.len > 0); if (fileField) { - const raw = fileField.value.toString('latin1').replace(/\x00+$/g, '').trim(); - if (raw) return path.basename(raw); + const raw = fileField.value.toString('latin1'); + const normalized = this.normalizeRequestedMediaPath(raw); + if (normalized) return normalized; } // Some clients may carry filename in another TLV field; scan all text values. @@ -368,7 +389,8 @@ class WTVPNM { const raw = field.value.toString('latin1').replace(/\x00+/g, ' ').trim(); const match = raw.match(/([A-Za-z0-9_\-\.\/]+\.(?:ra|ray|rm|ram))/i); if (match) { - return path.basename(match[1]); + const normalized = this.normalizeRequestedMediaPath(match[1]); + if (normalized) return normalized; } } @@ -379,8 +401,8 @@ class WTVPNM { scanRawForMediaName(rawData) { if (!Buffer.isBuffer(rawData)) return null; const str = rawData.toString('latin1'); - const match = str.match(/([A-Za-z0-9_\-\.]+\.(?:ra|ray|rm|ram))(?:[^A-Za-z0-9]|$)/i); - return match ? path.basename(match[1]) : null; + const match = str.match(/([A-Za-z0-9_\-\.\/]+\.(?:ra|ray|rm|ram))(?:[^A-Za-z0-9]|$)/i); + return match ? this.normalizeRequestedMediaPath(match[1]) : null; } getClientChallenge(fields) { @@ -423,6 +445,7 @@ class WTVPNM { const base = this.wtvshared.getAbsolutePath(serviceVaultDir, vault); for (const variant of extensionVariants) { const candidate = this.wtvshared.makeSafePath(base, variant); + this.debugLog('testing media candidate', candidate); if (candidate && fs.existsSync(candidate) && fs.lstatSync(candidate).isFile()) { if (this.service_config.debug) { this.debugLog('media file found', variant, '->', candidate); @@ -438,12 +461,12 @@ class WTVPNM { } getMediaNameVariants(requestedMedia) { - const base = path.basename(requestedMedia || '').trim(); - if (!base) return []; + const requestedPath = this.normalizeRequestedMediaPath(requestedMedia); + if (!requestedPath) return []; - const ext = path.extname(base).toLowerCase(); - const stem = ext.length > 0 ? base.slice(0, -ext.length) : base; - const variants = [base]; + const ext = path.posix.extname(requestedPath).toLowerCase(); + const stem = ext.length > 0 ? requestedPath.slice(0, -ext.length) : requestedPath; + const variants = [requestedPath]; if (ext === '.ray') variants.push(`${stem}.ra`); if (ext === '.ram') variants.push(`${stem}.ra`); @@ -1147,7 +1170,12 @@ class WTVPNM { const serverChallenge = session?.serverChallenge || 0; const challengeBuf = Buffer.from(challenge, 'latin1'); const requestedMedia = session?.requestedMedia || ''; - const resolvedMedia = session?.mediaPath ? path.basename(session.mediaPath) : ''; + const requestedMediaPath = this.normalizeRequestedMediaPath(requestedMedia); + const resolvedBase = session?.mediaPath ? path.basename(session.mediaPath) : ''; + const requestedDir = requestedMediaPath ? path.posix.dirname(requestedMediaPath) : ''; + const resolvedMedia = resolvedBase + ? (requestedDir && requestedDir !== '.' ? `${requestedDir}/${resolvedBase}` : resolvedBase) + : requestedMediaPath; const responseSource = resolvedMedia || requestedMedia || challenge; const respSrcBuf = Buffer.from(responseSource, 'latin1'); const timestamp = this.getClientTimestamp(session?.pnaFields) ?? Math.floor(Date.now() / 1000); @@ -1159,7 +1187,8 @@ class WTVPNM { this.debugLog('session token seed', session?.id || '?', `clientChallenge=${challenge}`, - `requestedMedia=${requestedMedia || '(fallback:clientChallenge)'}`, + `requestedMedia=${requestedMedia}`, + `responseSource=${responseSource}`, `serverChallenge=${serverChallenge.toString(16)}`, `v12=${v12}`, `resp1=${resp1}`, `initMD5=${initMD5}`); diff --git a/zefie_wtvp_minisrv/includes/config.json b/zefie_wtvp_minisrv/includes/config.json index 9fb48db5..89447d9f 100644 --- a/zefie_wtvp_minisrv/includes/config.json +++ b/zefie_wtvp_minisrv/includes/config.json @@ -401,7 +401,7 @@ "protocol_handler": "pnm", "descriptor_after_hello_ms": 85, "burst_prestart_ms": 5000, - "debug": false, + "debug": true, "allow_indexing": true } },