add sample realaudio, fix ragen

This commit is contained in:
zefie
2026-04-21 11:03:17 -04:00
parent f902466fdb
commit 6470f83c4b
23 changed files with 107 additions and 19 deletions

View File

@@ -16,13 +16,58 @@ if (minisrv_config.config.ServiceVaults) {
throw ("ERROR: No Service Vaults defined!"); 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 url_path = request_headers.request_url.split('?')[0];
const pathParts = url_path.split('/').filter(p => p); const pathParts = url_path.split('/').filter(p => p);
const serviceName = pathParts.length > 0 ? pathParts[0] : ''; const serviceName = pathParts.length > 0 ? pathParts[0] : '';
const remainingPath = '/' + pathParts.slice(1).join('/'); let remainingPath = '/' + pathParts.slice(1).join('/');
const filename = remainingPath.split('/').pop().replace('.ram', ''); const hadTrailingSlash = request_headers.request_url.endsWith('/');
const directory = remainingPath.endsWith('/') || !filename ? remainingPath.replace(/\/$/, '') : remainingPath.substring(0, remainingPath.lastIndexOf('/'));
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; let fileFound = false;
const extensions = ['.ra', '.rm']; 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'))) { if (fs.statSync(fullPath).isFile() && (file.endsWith('.ra') || file.endsWith('.rm'))) {
const baseFileName = file.substring(0, file.lastIndexOf('.')); const baseFileName = file.substring(0, file.lastIndexOf('.'));
allFiles.push(baseFileName + '.ram'); 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) { if (allFiles.length > 0) {
headers = `200 OK headers = `200 OK
Content-type: text/html`; Content-type: text/html`;
data = `<html><body><h1>RealAudio Files on this minisrv</h1><ul>${allFiles.map(f => `<li><a href="${f}">${f}</a></li>`).join('')}</ul></body></html>`; data = `<html>
<body bgcolor="#110e1f" text="#44a1cc" link="36d5ff" vlink="36d5ff" vspace=0>
<display nosave nosend>
<title>RealAudio Files on this Service</title>
<sidebar width=20%>
<img src="wtv-tricks:/images/Realaudio_bg.gif">
</sidebar>
<br>
<br>
<h1>RealAudio Files on this Service</h1><ul>${(directory === "") ? "" : `<li><a href="../">../</a></li>\n`}${allFiles.map(f => `<li><a href="${(directory === "") ? f : `${strippedSubDir}${directory}/${f}`}">${f}</a></li>`).join("\n")}</ul></body></html>`;
} else { } else {
headers = `404 Not Found headers = `404 Not Found
Content-type: text/html`; Content-type: text/html`;
@@ -61,7 +117,7 @@ Content-type: text/html`;
// Original file search logic // Original file search logic
for (const pnmVault of pnmVaults) { for (const pnmVault of pnmVaults) {
for (const ext of extensions) { 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); console.log("DEBUG: Checking for file", filePath);
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
fileFound = true; fileFound = true;
@@ -76,8 +132,11 @@ Content-type: text/html`;
headers = `404 Not Found headers = `404 Not Found
Content-type: text/html`; Content-type: text/html`;
} else { } 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 headers = `200 OK
Content-type: audio/x-pn-realaudio` Content-type: audio/x-pn-realaudio`
data = `pnm://${minisrv_config.config.service_ip}:${minisrv_config.services['pnm'].port}/${filename + path.extname(resolvedPath)}`; data = pnmURL;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -352,14 +352,35 @@ class WTVPNM {
socket.write(headers + body, () => socket.end()); 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) { getRequestedMediaName(fields, rawData) {
if (!Array.isArray(fields) || fields.length === 0) return this.scanRawForMediaName(rawData); if (!Array.isArray(fields) || fields.length === 0) return this.scanRawForMediaName(rawData);
// Field 0x52 (82) carries the requested file name in observed captures. // Field 0x52 (82) carries the requested file name in observed captures.
const fileField = fields.find((f) => f && f.id === 82 && f.len > 0); const fileField = fields.find((f) => f && f.id === 82 && f.len > 0);
if (fileField) { if (fileField) {
const raw = fileField.value.toString('latin1').replace(/\x00+$/g, '').trim(); const raw = fileField.value.toString('latin1');
if (raw) return path.basename(raw); const normalized = this.normalizeRequestedMediaPath(raw);
if (normalized) return normalized;
} }
// Some clients may carry filename in another TLV field; scan all text values. // 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 raw = field.value.toString('latin1').replace(/\x00+/g, ' ').trim();
const match = raw.match(/([A-Za-z0-9_\-\.\/]+\.(?:ra|ray|rm|ram))/i); const match = raw.match(/([A-Za-z0-9_\-\.\/]+\.(?:ra|ray|rm|ram))/i);
if (match) { 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) { scanRawForMediaName(rawData) {
if (!Buffer.isBuffer(rawData)) return null; if (!Buffer.isBuffer(rawData)) return null;
const str = rawData.toString('latin1'); const str = rawData.toString('latin1');
const match = str.match(/([A-Za-z0-9_\-\.]+\.(?:ra|ray|rm|ram))(?:[^A-Za-z0-9]|$)/i); const match = str.match(/([A-Za-z0-9_\-\.\/]+\.(?:ra|ray|rm|ram))(?:[^A-Za-z0-9]|$)/i);
return match ? path.basename(match[1]) : null; return match ? this.normalizeRequestedMediaPath(match[1]) : null;
} }
getClientChallenge(fields) { getClientChallenge(fields) {
@@ -423,6 +445,7 @@ class WTVPNM {
const base = this.wtvshared.getAbsolutePath(serviceVaultDir, vault); const base = this.wtvshared.getAbsolutePath(serviceVaultDir, vault);
for (const variant of extensionVariants) { for (const variant of extensionVariants) {
const candidate = this.wtvshared.makeSafePath(base, variant); const candidate = this.wtvshared.makeSafePath(base, variant);
this.debugLog('testing media candidate', candidate);
if (candidate && fs.existsSync(candidate) && fs.lstatSync(candidate).isFile()) { if (candidate && fs.existsSync(candidate) && fs.lstatSync(candidate).isFile()) {
if (this.service_config.debug) { if (this.service_config.debug) {
this.debugLog('media file found', variant, '->', candidate); this.debugLog('media file found', variant, '->', candidate);
@@ -438,12 +461,12 @@ class WTVPNM {
} }
getMediaNameVariants(requestedMedia) { getMediaNameVariants(requestedMedia) {
const base = path.basename(requestedMedia || '').trim(); const requestedPath = this.normalizeRequestedMediaPath(requestedMedia);
if (!base) return []; if (!requestedPath) return [];
const ext = path.extname(base).toLowerCase(); const ext = path.posix.extname(requestedPath).toLowerCase();
const stem = ext.length > 0 ? base.slice(0, -ext.length) : base; const stem = ext.length > 0 ? requestedPath.slice(0, -ext.length) : requestedPath;
const variants = [base]; const variants = [requestedPath];
if (ext === '.ray') variants.push(`${stem}.ra`); if (ext === '.ray') variants.push(`${stem}.ra`);
if (ext === '.ram') variants.push(`${stem}.ra`); if (ext === '.ram') variants.push(`${stem}.ra`);
@@ -1147,7 +1170,12 @@ class WTVPNM {
const serverChallenge = session?.serverChallenge || 0; const serverChallenge = session?.serverChallenge || 0;
const challengeBuf = Buffer.from(challenge, 'latin1'); const challengeBuf = Buffer.from(challenge, 'latin1');
const requestedMedia = session?.requestedMedia || ''; 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 responseSource = resolvedMedia || requestedMedia || challenge;
const respSrcBuf = Buffer.from(responseSource, 'latin1'); const respSrcBuf = Buffer.from(responseSource, 'latin1');
const timestamp = this.getClientTimestamp(session?.pnaFields) ?? Math.floor(Date.now() / 1000); const timestamp = this.getClientTimestamp(session?.pnaFields) ?? Math.floor(Date.now() / 1000);
@@ -1159,7 +1187,8 @@ class WTVPNM {
this.debugLog('session token seed', session?.id || '?', this.debugLog('session token seed', session?.id || '?',
`clientChallenge=${challenge}`, `clientChallenge=${challenge}`,
`requestedMedia=${requestedMedia || '(fallback:clientChallenge)'}`, `requestedMedia=${requestedMedia}`,
`responseSource=${responseSource}`,
`serverChallenge=${serverChallenge.toString(16)}`, `serverChallenge=${serverChallenge.toString(16)}`,
`v12=${v12}`, `v12=${v12}`,
`resp1=${resp1}`, `initMD5=${initMD5}`); `resp1=${resp1}`, `initMD5=${initMD5}`);

View File

@@ -401,7 +401,7 @@
"protocol_handler": "pnm", "protocol_handler": "pnm",
"descriptor_after_hello_ms": 85, "descriptor_after_hello_ms": 85,
"burst_prestart_ms": 5000, "burst_prestart_ms": 5000,
"debug": false, "debug": true,
"allow_indexing": true "allow_indexing": true
} }
}, },