|
|
|
@ -1,6 +1,9 @@
|
|
|
|
|
|
|
|
|
|
import { spawn } from 'child_process';
|
|
|
|
|
import * as dns from 'dns';
|
|
|
|
|
import { request } from 'http';
|
|
|
|
|
import { Readable, Writable } from 'node:stream';
|
|
|
|
|
import { Duplex } from 'stream';
|
|
|
|
|
import type { FileSystemConfig } from './fileSystemConfig';
|
|
|
|
|
import { Logging } from './logging';
|
|
|
|
|
import { toPromise } from './toPromise';
|
|
|
|
@ -14,9 +17,20 @@ async function resolveHostname(hostname: string): Promise<string> {
|
|
|
|
|
|
|
|
|
|
function validateConfig(config: FileSystemConfig) {
|
|
|
|
|
if (!config.proxy) throw new Error(`Missing field 'config.proxy'`);
|
|
|
|
|
if (!config.proxy.host) throw new Error(`Missing field 'config.proxy.host'`);
|
|
|
|
|
if (!config.proxy.port) throw new Error(`Missing field 'config.proxy.port'`);
|
|
|
|
|
if (!config.proxy.type) throw new Error(`Missing field 'config.proxy.type'`);
|
|
|
|
|
switch (config.proxy.type) {
|
|
|
|
|
case 'http':
|
|
|
|
|
case 'socks4':
|
|
|
|
|
case 'socks5':
|
|
|
|
|
if (!config.proxy.host) throw new Error(`Missing field 'config.proxy.host'`);
|
|
|
|
|
if (!config.proxy.port) throw new Error(`Missing field 'config.proxy.port'`);
|
|
|
|
|
break;
|
|
|
|
|
case 'command':
|
|
|
|
|
if (!config.proxy.command) throw new Error(`Missing field 'config.proxy.command'`);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw new Error(`Unrecognized proxy type '${config.proxy!.type}'`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function socks(config: FileSystemConfig): Promise<NodeJS.ReadWriteStream> {
|
|
|
|
@ -73,3 +87,35 @@ export function http(config: FileSystemConfig): Promise<NodeJS.ReadWriteStream>
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class ReadWriteWrapper extends Duplex {
|
|
|
|
|
constructor(protected _readable: Readable, protected _writable: Writable) {
|
|
|
|
|
super();
|
|
|
|
|
_readable.once('finish', () => this.end());
|
|
|
|
|
_writable.once('end', () => this.push(null));
|
|
|
|
|
_readable.once('error', e => this.emit('error', e));
|
|
|
|
|
_writable.once('error', e => this.emit('error', e));
|
|
|
|
|
this.once('close', () => _writable.end());
|
|
|
|
|
}
|
|
|
|
|
_destroy() { this._readable.destroy(); this._writable.destroy(); }
|
|
|
|
|
_write(chunk: any, encoding: string, callback: any) {
|
|
|
|
|
return this._writable._write(chunk, encoding, callback);
|
|
|
|
|
}
|
|
|
|
|
_read(size: number) {
|
|
|
|
|
const chunk = this._readable.read();
|
|
|
|
|
if (chunk) this.push(chunk);
|
|
|
|
|
else this._readable.once('readable', () => this._read(size));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function command(config: FileSystemConfig): Promise<NodeJS.ReadWriteStream> {
|
|
|
|
|
Logging.info(`Creating ProxyCommand connection for ${config.name}`);
|
|
|
|
|
validateConfig(config);
|
|
|
|
|
const proxy = config.proxy!;
|
|
|
|
|
if (proxy.type !== 'command') throw new Error(`Expected config.proxy.type' to be 'command'`);
|
|
|
|
|
Logging.debug('\tcommand: ' + proxy.command);
|
|
|
|
|
const proc = spawn(proxy.command, { shell: true, stdio: ['pipe', 'pipe', 'inherit'] });
|
|
|
|
|
if (proc.killed) throw new Error(`ProxyCommand process died with exit code ${proc.exitCode}`);
|
|
|
|
|
if (!proc.pid) throw new Error(`ProxyCommand process did not spawn, possible exit code: ${proc.exitCode}`);
|
|
|
|
|
return new ReadWriteWrapper(proc.stdout, proc.stdin);
|
|
|
|
|
}
|
|
|
|
|