This commit is contained in:
zefie
2025-07-21 19:26:44 -04:00
parent 84a8abdb33
commit 2b2735264a
2 changed files with 71 additions and 71 deletions

View File

@@ -1095,8 +1095,9 @@ minisrv-no-mail-count: true`;
processPath(socket, urlToPath, request_headers, service_name, shared_romcache, pc_services); processPath(socket, urlToPath, request_headers, service_name, shared_romcache, pc_services);
} else if (shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0 || (use_external_proxy == true && shortURL.indexOf(service_name + "://") >= 0) && !pc_services) { } else if (shortURL.indexOf('http://') >= 0 || shortURL.indexOf('https://') >= 0 || (use_external_proxy == true && shortURL.indexOf(service_name + "://") >= 0) && !pc_services) {
doHTTPProxy(socket, request_headers); doHTTPProxy(socket, request_headers);
} else if (shortURL.startsWith('ftp://')) { } else if (shortURL.startsWith('ftp://')) {
var wtvftp = new WTVFTP(minisrv_config, sendToClient); if (minisrv_config.config.debug_flags.show_headers) console.debug(" * Incoming FTP request on WTVP socket ID", socket.id, await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers))));
var wtvftp = new WTVFTP(wtvshared, sendToClient);
wtvftp.handleFTPRequest(socket, request_headers); wtvftp.handleFTPRequest(socket, request_headers);
} else if (shortURL.indexOf('file://') >= 0) { } else if (shortURL.indexOf('file://') >= 0) {
shortURL = shortURL.replace("file://",'').replace("romcache", "ROMCache"); shortURL = shortURL.replace("file://",'').replace("romcache", "ROMCache");

View File

@@ -1,3 +1,5 @@
const dns = require('dns');
class WTVFTP { class WTVFTP {
wtvshared = null; wtvshared = null;
wtvmime = null; wtvmime = null;
@@ -14,18 +16,15 @@ class WTVFTP {
const WTVMime = require("./WTVMime.js"); const WTVMime = require("./WTVMime.js");
this.url = require('url'); this.url = require('url');
this.ftp = require('ftp'); this.ftp = require('ftp');
this.wtvshared = new WTVShared(); this.wtvshared = new WTVShared(minisrv_config);
this.wtvmime = new WTVMime(); this.wtvmime = new WTVMime(minisrv_config);
} }
handleFTPRequest(socket, request_headers) { handleFTPRequest(socket, request_headers) {
// Handle the FTP request here
// Assume request_headers.url contains the FTP URL
this.request_headers = request_headers; this.request_headers = request_headers;
const ftpUrl = request_headers.request_url; const ftpUrl = request_headers.request_url;
const parsed = this.url.parse(ftpUrl); const parsed = this.url.parse(ftpUrl);
// Extract user, pass, and host
let user = null; let user = null;
let pass = null; let pass = null;
let host = parsed.hostname; let host = parsed.hostname;
@@ -36,10 +35,6 @@ class WTVFTP {
pass = password || null; pass = password || null;
} }
// Example usage: log the parsed values
// You can now use user, pass, and host as needed
if (!user && !pass) { if (!user && !pass) {
user = "anonymous"; user = "anonymous";
pass = "anonymous@eff.org"; pass = "anonymous@eff.org";
@@ -51,73 +46,70 @@ class WTVFTP {
let dir = path; let dir = path;
let filename = null; let filename = null;
// Determine if path is a file or directory
if (path && path !== '/') { if (path && path !== '/') {
const parts = path.split('/'); const parts = path.split('/');
if (parts[parts.length - 1] && !path.endsWith('/')) { if (parts[parts.length - 1] && !path.endsWith('/')) {
filename = parts.pop(); filename = parts.pop();
dir = parts.join('/') || '/'; dir = parts.join('/') || '/';
} }
} }
ftpClient.on('ready', () => { ftpClient.on('ready', () => {
if (filename) { if (filename) {
var totalsize = 0; var totalsize = 0;
// Change to directory and get file ftpClient.cwd(dir, (err) => {
ftpClient.cwd(dir, (err) => { if (err) {
if (err) { this.sendToClient(socket, { 'Status': '500 Failed to change directory', 'Content-Type': 'text/plain' }, 'Failed to change directory');
this.sendToClient(socket, { 'Status': '500 Failed to change directory', 'Content-Type': 'text/plain' }, 'Failed to change directory');
ftpClient.end();
return;
}
ftpClient.get(filename, (err, stream) => {
if (err) {
this.sendToClient(socket, { 'Status': '404 File not found', 'Content-Type': 'text/plain' }, 'File not found');
ftpClient.end();
return;
}
const chunks = [];
stream.on('data', (chunk) => {
chunks.push(chunk);
totalsize += chunk.length;
if (totalsize > 1024 * 1024 * 4) {
this.sendToClient(socket, { 'Status': '413 The file chosen contains too much information to be used.', 'Content-Type': 'text/plain' }, 'File too large');
ftpClient.end(); ftpClient.end();
return; return;
} }
ftpClient.get(filename, (err, stream) => {
if (err) {
this.sendToClient(socket, { 'Status': '404 File not found', 'Content-Type': 'text/plain' }, 'File not found');
ftpClient.end();
return;
}
const chunks = [];
stream.on('data', (chunk) => {
chunks.push(chunk);
totalsize += chunk.length;
if (totalsize > 1024 * 1024 * 4) {
this.sendToClient(socket, { 'Status': '413 The file chosen contains too much information to be used.', 'Content-Type': 'text/plain' }, 'File too large');
ftpClient.end();
return;
}
});
stream.on('end', () => {
const buffer = Buffer.concat(chunks);
const mime = this.wtvmime.detectMimeTypeFromBuffer(buffer);
this.sendToClient(
socket,
{
'Status': 200,
'Content-Type': mime || 'application/octet-stream',
'Content-Disposition': `attachment; filename="${filename}"`
},
buffer
);
ftpClient.end();
});
stream.on('error', () => {
this.sendToClient(socket, { 'Status': '500 Error reading file', 'Content-Type': 'text/plain' }, 'Error reading file');
ftpClient.end();
});
});
}); });
stream.on('end', () => {
const buffer = Buffer.concat(chunks);
const mime = this.wtvmime.detectMimeTypeFromBuffer(buffer);
this.sendToClient(
socket,
{
'Status': 200,
'Content-Type': mime || 'application/octet-stream',
'Content-Disposition': `attachment; filename="${filename}"`
},
buffer
);
ftpClient.end();
});
stream.on('error', () => {
this.sendToClient(socket, { 'Status': '500 Error reading file', 'Content-Type': 'text/plain' }, 'Error reading file');
ftpClient.end();
});
});
});
} else { } else {
// List directory ftpClient.list(dir, (err, list) => {
ftpClient.list(dir, (err, list) => { if (err) {
if (err) { this.sendToClient(socket, { 'Status': '500 Failed to list directory', 'Content-Type': 'text/plain' }, 'Failed to list directory');
this.sendToClient(socket, { 'Status': '500 Failed to list directory', 'Content-Type': 'text/plain' }, 'Failed to list directory'); ftpClient.end();
ftpClient.end(); return;
return; }
} const html = this.formatDirectoryListing(list);
const html = this.formatDirectoryListing(list); this.sendToClient(socket, { 'Status': '200 OK', 'Content-Type': 'text/html' }, html);
this.sendToClient(socket, { 'Status': '200 OK', 'Content-Type': 'text/html' }, html); ftpClient.end();
ftpClient.end(); });
});
} }
}); });
@@ -125,16 +117,23 @@ class WTVFTP {
this.sendToClient(socket, { 'Status': '500 FTP connection error', 'Content-Type': 'text/plain' }, 'FTP connection error'); this.sendToClient(socket, { 'Status': '500 FTP connection error', 'Content-Type': 'text/plain' }, 'FTP connection error');
}); });
ftpClient.connect({ // FIX: Resolve host to IPv4 address before connecting
host: host, dns.lookup(host, { family: 4 }, (err, address) => {
port: port, if (err) {
user: user, this.sendToClient(socket, { 'Status': '500 DNS resolution error', 'Content-Type': 'text/plain' }, 'DNS resolution error');
password: pass return;
}
ftpClient.connect({
host: address,
port: port,
user: user,
password: pass
});
}); });
} }
formatDirectoryListing(list) { formatDirectoryListing(list) {
// Format the directory listing as needed
let html = `<html> let html = `<html>
<head> <head>
<title>FTP Directory Listing</title> <title>FTP Directory Listing</title>
@@ -183,4 +182,4 @@ class WTVFTP {
} }
} }
module.exports = WTVFTP; module.exports = WTVFTP;