@rynn-k/proxy-agent
Version:
Efficient proxy rotation agent for Node.js with seamless axios integration
235 lines (195 loc) • 5.91 kB
JavaScript
const net = require('net');
/**
* Validate IP address
* @param {string} ip - IP address to validate
* @returns {boolean} True if valid IP
*/
function isValidIP(ip) {
return net.isIP(ip) !== 0;
}
/**
* Validate port number
* @param {string|number} port - Port to validate
* @returns {boolean} True if valid port
*/
function isValidPort(port) {
const portNum = parseInt(port, 10);
return !isNaN(portNum) && portNum > 0 && portNum <= 65535;
}
/**
* Validate proxy object
* @param {Object} proxy - Proxy object to validate
* @returns {boolean} True if valid proxy
*/
function validateProxy(proxy) {
if (!proxy || typeof proxy !== 'object') {
return false;
}
if (!proxy.ip || !proxy.port) {
return false;
}
if (!isValidIP(proxy.ip)) {
return false;
}
if (!isValidPort(proxy.port)) {
return false;
}
if (proxy.protocol) {
const validProtocols = ['http', 'https', 'socks4', 'socks5'];
if (!validProtocols.includes(proxy.protocol.toLowerCase())) {
return false;
}
}
if (proxy.username || proxy.password) {
if (!proxy.username || !proxy.password) {
return false;
}
if (typeof proxy.username !== 'string' || typeof proxy.password !== 'string') {
return false;
}
if (proxy.username.length === 0 || proxy.password.length === 0) {
return false;
}
}
return true;
}
/**
* Parse proxy line from file
* @param {string} line - Proxy line from file
* @param {number} lineNumber - Line number for error reporting
* @param {string} defaultProtocol - Default protocol if not specified
* @returns {Object} Parsed proxy object
*/
function parseProxyLine(line, lineNumber = 0, defaultProtocol = 'http') {
line = line.trim();
const protocolMatch = line.match(/^(https?|socks4|socks5):\/\//i);
if (protocolMatch) {
try {
const url = new URL(line);
const protocol = url.protocol.replace(':', '').toLowerCase();
const proxy = {
ip: url.hostname,
port: parseInt(url.port, 10),
protocol: protocol,
url: line
};
if (url.username && url.password) {
proxy.username = decodeURIComponent(url.username);
proxy.password = decodeURIComponent(url.password);
}
return proxy;
} catch (error) {
throw new Error(`Invalid proxy URL format at line ${lineNumber}: ${error.message}`);
}
}
const parts = line.split(':');
if (parts.length < 2) {
throw new Error(`Invalid proxy format at line ${lineNumber}: expected at least ip:port`);
}
const firstPart = parts[0].toLowerCase();
if (['http', 'https', 'socks4', 'socks5'].includes(firstPart)) {
if (parts.length === 3) {
const protocol = parts[0].toLowerCase();
const ip = parts[1].trim();
const port = parts[2].trim();
if (!ip || !port) {
throw new Error(`Empty IP or port at line ${lineNumber}`);
}
return {
ip,
port: parseInt(port, 10),
protocol,
url: `${protocol}://${ip}:${port}`
};
} else if (parts.length === 5) {
const protocol = parts[0].toLowerCase();
const ip = parts[1].trim();
const port = parts[2].trim();
const username = parts[3].trim();
const password = parts[4].trim();
if (!ip || !port) {
throw new Error(`Empty IP or port at line ${lineNumber}`);
}
if (!username || !password) {
throw new Error(`Empty username or password at line ${lineNumber}`);
}
const encodedUsername = encodeURIComponent(username);
const encodedPassword = encodeURIComponent(password);
return {
ip,
port: parseInt(port, 10),
username,
password,
protocol,
url: `${protocol}://${encodedUsername}:${encodedPassword}@${ip}:${port}`
};
} else {
throw new Error(`Invalid proxy format at line ${lineNumber}: unsupported format with ${parts.length} parts`);
}
}
const ip = parts[0].trim();
const port = parts[1].trim();
if (!ip || !port) {
throw new Error(`Empty IP or port at line ${lineNumber}`);
}
if (parts.length === 2) {
return {
ip,
port: parseInt(port, 10),
protocol: defaultProtocol,
url: `${defaultProtocol}://${ip}:${port}`
};
}
if (parts.length === 4) {
const username = parts[2].trim();
const password = parts[3].trim();
if (!username || !password) {
throw new Error(`Empty username or password at line ${lineNumber}`);
}
const encodedUsername = encodeURIComponent(username);
const encodedPassword = encodeURIComponent(password);
return {
ip,
port: parseInt(port, 10),
username,
password,
protocol: defaultProtocol,
url: `${defaultProtocol}://${encodedUsername}:${encodedPassword}@${ip}:${port}`
};
}
throw new Error(`Invalid proxy format at line ${lineNumber}: unsupported format with ${parts.length} parts`);
}
/**
* Parse proxy URL
* @param {string} proxyUrl - Proxy URL to parse
* @returns {Object} Parsed proxy object
*/
function parseProxyUrl(proxyUrl) {
try {
const url = new URL(proxyUrl);
const protocol = url.protocol.replace(':', '').toLowerCase();
if (!['http', 'https', 'socks4', 'socks5'].includes(protocol)) {
throw new Error(`Unsupported protocol: ${protocol}`);
}
const proxy = {
ip: url.hostname,
port: parseInt(url.port, 10),
protocol: protocol,
url: proxyUrl
};
if (url.username && url.password) {
proxy.username = decodeURIComponent(url.username);
proxy.password = decodeURIComponent(url.password);
}
return proxy;
} catch (error) {
throw new Error(`Invalid proxy URL: ${error.message}`);
}
}
module.exports = {
isValidIP,
isValidPort,
validateProxy,
parseProxyLine,
parseProxyUrl
};