add POST to client_sim
This commit is contained in:
@@ -19,12 +19,14 @@ const AdmZip = require('adm-zip');
|
||||
* using the WTVP protocol with proper authentication and service discovery.
|
||||
*/
|
||||
class WebTVClientSimulator {
|
||||
constructor(host, port, ssid, url, outputFile = null, maxRedirects = 10, useEncryption = false, request_type_download = false, debug = false, tricks = false, followImages = false, followAll = false, maxDepth = 5, maxRetries = 5, requestDelay = 250, boxType = null, username = null, keepgz = false) {
|
||||
constructor(host, port, ssid, url, outputFile = null, maxRedirects = 10, useEncryption = false, request_type_download = false, debug = false, tricks = false, followImages = false, followAll = false, maxDepth = 5, maxRetries = 5, requestDelay = 250, boxType = null, username = null, keepgz = false, request_type_post = false, postData = null) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.ssid = ssid;
|
||||
this.url = url;
|
||||
this.keepgz = keepgz;
|
||||
this.request_type_post = request_type_post;
|
||||
this.postData = postData;
|
||||
this.request_type_download = request_type_download;
|
||||
this.outputFile = outputFile;
|
||||
this.followImages = followImages;
|
||||
@@ -367,7 +369,7 @@ class WebTVClientSimulator {
|
||||
if (this.encryptionEnabled) {
|
||||
// For encrypted responses, we need to handle differently
|
||||
if (!responseHandled) {
|
||||
const result = this.handleEncryptedResponse(responseData, resolve, reject);
|
||||
const result = this.handleEncryptedResponse(responseData, resolve, reject, false, skipRedirects);
|
||||
if (result === true) { // If response was handled
|
||||
responseHandled = true;
|
||||
cleanupListeners();
|
||||
@@ -430,7 +432,7 @@ class WebTVClientSimulator {
|
||||
cleanupListeners();
|
||||
// Force processing regardless of Content-Length completeness
|
||||
try {
|
||||
this.handleEncryptedResponse(responseData, resolve, reject, true);
|
||||
this.handleEncryptedResponse(responseData, resolve, reject, true, skipRedirects);
|
||||
} catch (e) {
|
||||
console.error('Error handling encrypted response on close:', e);
|
||||
reject(e);
|
||||
@@ -628,7 +630,7 @@ class WebTVClientSimulator {
|
||||
/**
|
||||
* Handle encrypted response data
|
||||
*/
|
||||
handleEncryptedResponse(responseData, resolve, reject, forceProcess = false) {
|
||||
handleEncryptedResponse(responseData, resolve, reject, forceProcess = false, skipRedirects = false) {
|
||||
try {
|
||||
// Find header/body split using CRLF CRLF (\r\n\r\n) or fallback to LF LF (\n\n)
|
||||
let idx = -1;
|
||||
@@ -683,7 +685,7 @@ class WebTVClientSimulator {
|
||||
this.debugLog(`LZPF timeout - processing response with ${bodyBuffer.length} bytes`);
|
||||
this.lzpfTimeoutId = null;
|
||||
// Force processing by calling again with forceProcess = true
|
||||
this.handleEncryptedResponse(responseData, resolve, reject, true);
|
||||
this.handleEncryptedResponse(responseData, resolve, reject, true, false);
|
||||
}, 100);
|
||||
return false;
|
||||
}
|
||||
@@ -747,7 +749,7 @@ class WebTVClientSimulator {
|
||||
// The socket will be managed by the socket pool
|
||||
|
||||
// Check for redirects (Location header)
|
||||
if ((headers['Location'] || headers['location']) && statusLine.startsWith('302')) {
|
||||
if ((headers['Location'] || headers['location']) && statusLine.startsWith('302') && !skipRedirects) {
|
||||
const redirectUrl = headers['Location'] || headers['location'];
|
||||
this.debugLog(`Following redirect to: ${redirectUrl}`);
|
||||
this.redirectCount++;
|
||||
@@ -1205,70 +1207,85 @@ class WebTVClientSimulator {
|
||||
*/
|
||||
async fetchTargetUrl() {
|
||||
console.log(`Fetching target URL: ${this.url}`);
|
||||
if (this.useTricksAccess) {
|
||||
|
||||
// Handle special case for tricks access with POST
|
||||
if (this.useTricksAccess && this.request_type_post) {
|
||||
this.debugLog('Using tricks access with POST - first GET the tricks page, then POST to wtv-visit');
|
||||
|
||||
// First, GET the tricks page to get the wtv-visit URL
|
||||
const tricksUrl = `wtv-tricks:/access?url=${encodeURIComponent(this.url)}`;
|
||||
const match = tricksUrl.match(/^([\w-]+):\/?(.*)/);
|
||||
if (match) {
|
||||
const serviceName = match[1];
|
||||
const path = '/' + (match[2] || '');
|
||||
|
||||
try {
|
||||
// GET the tricks page (skip automatic redirects so we can handle them manually)
|
||||
const tricksResult = await this.makeRequestWithRetry(serviceName, path, null, true);
|
||||
|
||||
// Extract wtv-visit URL or Location from headers
|
||||
if (tricksResult.headers['Location'] || tricksResult.headers['location']) {
|
||||
const visitUrl = tricksResult.headers['Location'] || tricksResult.headers['location'];
|
||||
this.debugLog(`Got Location URL from tricks page: ${visitUrl}`);
|
||||
|
||||
// Now POST to the Location URL
|
||||
const visitMatch = visitUrl.match(/^([\w-]+):\/?(.*)/);
|
||||
if (visitMatch) {
|
||||
const visitServiceName = visitMatch[1];
|
||||
const visitPath = '/' + (visitMatch[2] || '');
|
||||
|
||||
this.debugLog(`Making POST request to Location URL: ${visitUrl} with data: ${this.postData}`);
|
||||
const result = await this.makeRequestWithRetry(visitServiceName, visitPath, this.postData, false);
|
||||
return this.handleTargetUrlResponse(result);
|
||||
}
|
||||
} else if (tricksResult.headers['wtv-visit']) {
|
||||
const visitUrl = tricksResult.headers['wtv-visit'];
|
||||
this.debugLog(`Got wtv-visit URL from tricks page: ${visitUrl}`);
|
||||
|
||||
// Now POST to the wtv-visit URL
|
||||
const visitMatch = visitUrl.match(/^([\w-]+):\/?(.*)/);
|
||||
if (visitMatch) {
|
||||
const visitServiceName = visitMatch[1];
|
||||
const visitPath = '/' + (visitMatch[2] || '');
|
||||
|
||||
this.debugLog(`Making POST request to wtv-visit URL: ${visitUrl} with data: ${this.postData}`);
|
||||
const result = await this.makeRequestWithRetry(visitServiceName, visitPath, this.postData, false);
|
||||
return this.handleTargetUrlResponse(result);
|
||||
}
|
||||
} else {
|
||||
throw new Error('No Location or wtv-visit header found in tricks page response');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during tricks access with POST:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} else if (this.useTricksAccess && !this.request_type_post) {
|
||||
// Regular tricks access (GET)
|
||||
this.debugLog('Using tricks access for target URL');
|
||||
this.url = `wtv-tricks:/access?url=${encodeURIComponent(this.url)}`;
|
||||
}
|
||||
|
||||
// Parse the target URL
|
||||
const match = this.url.match(/^([\w-]+):\/?(.*)/);
|
||||
if (match) {
|
||||
const serviceName = match[1];
|
||||
let path = '/' + (match[2] || '');
|
||||
|
||||
|
||||
this.debugLog(`Parsed target service: ${serviceName}, path: ${path}`);
|
||||
|
||||
try {
|
||||
const result = await this.makeRequestWithRetry(serviceName, path, null, false);
|
||||
|
||||
|
||||
// Handle the response
|
||||
if (result.body) {
|
||||
this.debugLog('\n*** Target URL Response Body ***');
|
||||
if (this.outputFile) {
|
||||
// Check if target URL returned a download-list and --follow is enabled
|
||||
const contentType = result.headers['content-type'] || '';
|
||||
const normalizedContentType = contentType.toLowerCase().split(';')[0].trim();
|
||||
const isDownloadList = normalizedContentType === 'wtv/download-list';
|
||||
|
||||
if (this.followAll) {
|
||||
// Store the main content first
|
||||
this.storeContent(this.url, result);
|
||||
|
||||
// Process all pending downloads
|
||||
await this.processAllDownloads();
|
||||
|
||||
// Create comprehensive archive
|
||||
await this.createComprehensiveArchive();
|
||||
} else if (this.followImages && isDownloadList) {
|
||||
this.debugLog('Target URL returned download-list content with --follow enabled, creating archive...');
|
||||
await this.createDownloadListArchive(result.body, result.headers);
|
||||
} else if (this.followImages) {
|
||||
await this.saveToFile(result.body, result.headers);
|
||||
} else {
|
||||
await this.saveToFile(result.body, result.headers);
|
||||
}
|
||||
console.log(`Content saved to: ${this.outputFile}`);
|
||||
} else {
|
||||
// Detect text content for CLI output
|
||||
const contentType = result.headers['content-type'] || '';
|
||||
if (/^text\//.test(contentType) || /json|xml|javascript||download-list/.test(contentType) || contentType === "x-wtv-addresses") {
|
||||
console.log(result.body.toString('utf8'));
|
||||
} else if (result.body.length === 0) {
|
||||
console.log('<empty response>');
|
||||
} else {
|
||||
console.log('<binary data>');
|
||||
}
|
||||
}
|
||||
// Determine if this should be a POST request
|
||||
const requestData = this.request_type_post ? this.postData : null;
|
||||
if (this.request_type_post) {
|
||||
this.debugLog(`Making POST request to ${serviceName}:${path} with data: ${requestData}`);
|
||||
} else {
|
||||
this.debugLog('No body content received from target URL');
|
||||
this.debugLog(`Making GET request to ${serviceName}:${path}`);
|
||||
}
|
||||
|
||||
this.debugLog('\n*** Request completed successfully ***');
|
||||
this.cleanup();
|
||||
process.exit(0);
|
||||
|
||||
return result;
|
||||
const result = await this.makeRequestWithRetry(serviceName, path, requestData, false);
|
||||
return this.handleTargetUrlResponse(result);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching target URL:', error);
|
||||
throw error;
|
||||
@@ -1278,6 +1295,59 @@ class WebTVClientSimulator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the response from the target URL
|
||||
*/
|
||||
async handleTargetUrlResponse(result) {
|
||||
// Handle the response
|
||||
if (result.body) {
|
||||
this.debugLog('\n*** Target URL Response Body ***');
|
||||
if (this.outputFile) {
|
||||
// Check if target URL returned a download-list and --follow is enabled
|
||||
const contentType = result.headers['content-type'] || '';
|
||||
const normalizedContentType = contentType.toLowerCase().split(';')[0].trim();
|
||||
const isDownloadList = normalizedContentType === 'wtv/download-list';
|
||||
|
||||
if (this.followAll) {
|
||||
// Store the main content first
|
||||
this.storeContent(this.url, result);
|
||||
|
||||
// Process all pending downloads
|
||||
await this.processAllDownloads();
|
||||
|
||||
// Create comprehensive archive
|
||||
await this.createComprehensiveArchive();
|
||||
} else if (this.followImages && isDownloadList) {
|
||||
this.debugLog('Target URL returned download-list content with --follow enabled, creating archive...');
|
||||
await this.createDownloadListArchive(result.body, result.headers);
|
||||
} else if (this.followImages) {
|
||||
await this.saveToFile(result.body, result.headers);
|
||||
} else {
|
||||
await this.saveToFile(result.body, result.headers);
|
||||
}
|
||||
console.log(`Content saved to: ${this.outputFile}`);
|
||||
} else {
|
||||
// Detect text content for CLI output
|
||||
const contentType = result.headers['content-type'] || '';
|
||||
if (/^text\//.test(contentType) || /json|xml|javascript||download-list/.test(contentType) || contentType === "x-wtv-addresses") {
|
||||
console.log(result.body.toString('utf8'));
|
||||
} else if (result.body.length === 0) {
|
||||
console.log('<empty response>');
|
||||
} else {
|
||||
console.log('<binary data>');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.debugLog('No body content received from target URL');
|
||||
}
|
||||
|
||||
this.debugLog('\n*** Request completed successfully ***');
|
||||
this.cleanup();
|
||||
process.exit(0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the content response without following redirects
|
||||
*/
|
||||
@@ -2613,7 +2683,9 @@ function parseArgs() {
|
||||
requestDelay: 250,
|
||||
debug: false,
|
||||
username: null,
|
||||
keepgz: false
|
||||
keepgz: false,
|
||||
request_type_post: false,
|
||||
postData: null
|
||||
};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
@@ -2696,6 +2768,14 @@ function parseArgs() {
|
||||
case '--keepgz':
|
||||
config.keepgz = true;
|
||||
break;
|
||||
case '--post':
|
||||
config.request_type_post = true;
|
||||
break;
|
||||
case '--data':
|
||||
if (i + 1 < args.length) {
|
||||
config.postData = args[++i];
|
||||
}
|
||||
break;
|
||||
case '--help':
|
||||
console.log(`
|
||||
WebTV Client Simulator
|
||||
@@ -2720,6 +2800,8 @@ Options:
|
||||
--retries <num> Maximum number of retries for ECONNREFUSED errors (default: 5)
|
||||
--delay <num> Delay between requests in milliseconds (default: 250)
|
||||
--keepgz Keep .gz files compressed when following wtv/download-list (default: false)
|
||||
--post Use POST method for the final target URL request
|
||||
--data <data> POST data to send with --post requests (required with --post)
|
||||
--debug Enable debug logging
|
||||
--help Show this help message
|
||||
|
||||
@@ -2727,11 +2809,19 @@ Example:
|
||||
node client_emu.js --host 192.168.1.100 --port 1615 --ssid 8100000000000001 --url wtv-home:/home --file output.html
|
||||
node client_emu.js --host 127.0.0.1 --url wtv-home:/home --file archive.zip --follow --debug
|
||||
node client_emu.js --host 127.0.0.1 --url wtv-home:/home --file complete.zip --follow-all --depth 2 --debug
|
||||
node client_emu.js --host 127.0.0.1 --url wtv-mail:/sendmail --post --data "to=user@example.com&subject=test&body=Hello" --file response.html
|
||||
node client_emu.js --host 127.0.0.1 --url wtv-mail:/sendmail --post --data "to=user@example.com&subject=test&body=Hello" --tricks
|
||||
`);
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate POST requirements
|
||||
if (config.request_type_post && !config.postData) {
|
||||
console.error('Error: --post requires --data to be specified');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -2740,7 +2830,7 @@ Example:
|
||||
*/
|
||||
async function main() {
|
||||
const config = parseArgs();
|
||||
const simulator = new WebTVClientSimulator(config.host, config.port, config.ssid, config.url, config.outputFile, config.maxRedirects, config.useEncryption, config.request_type_download, config.debug, config.useTricksAccess, config.followImages, config.followAll, config.maxDepth, config.maxRetries, config.requestDelay, config.boxType, config.username, config.keepgz);
|
||||
const simulator = new WebTVClientSimulator(config.host, config.port, config.ssid, config.url, config.outputFile, config.maxRedirects, config.useEncryption, config.request_type_download, config.debug, config.useTricksAccess, config.followImages, config.followAll, config.maxDepth, config.maxRetries, config.requestDelay, config.boxType, config.username, config.keepgz, config.request_type_post, config.postData);
|
||||
|
||||
// Handle graceful shutdown
|
||||
process.on('SIGINT', () => {
|
||||
|
||||
Reference in New Issue
Block a user