diff --git a/app.ts b/app.ts index e3da2cd7..db4a775a 100644 --- a/app.ts +++ b/app.ts @@ -7,7 +7,7 @@ import { connect as mongoConnect } from './db_connect'; import { resubscribeInvoices } from './ln'; import { logger } from './logger'; import { Telegraf } from 'telegraf'; -import { delay } from './util'; +import { delay, buildSocksProxyUrl } from './util'; import { imageCache } from './util/imageCache'; import { createIndexes } from './models/indexes'; import { CommunityContext } from './bot/modules/community/communityContext'; @@ -50,9 +50,7 @@ import { startMonitoring } from './monitoring'; const socksProxyHost = process.env.SOCKS_PROXY_HOST?.trim(); const telegramAgent = socksProxyHost ? (() => { - const proxyUrl = /^socks[45]?:\/\//i.test(socksProxyHost) - ? socksProxyHost - : `socks5://${socksProxyHost}`; + const proxyUrl = buildSocksProxyUrl(socksProxyHost); logger.info(`Using SOCKS proxy for Telegram API: ${proxyUrl}`); return new SocksProxyAgent(proxyUrl) as any; })() diff --git a/tests/util/index.spec.ts b/tests/util/index.spec.ts index 52b321ea..75cf8dd5 100644 --- a/tests/util/index.spec.ts +++ b/tests/util/index.spec.ts @@ -8,6 +8,7 @@ import { toKebabCase, getDetailedOrder, getUserI18nContext, + buildSocksProxyUrl, } from '../../util/index'; const { expect } = require('chai'); @@ -222,4 +223,33 @@ describe('Utility Functions', () => { expect(ctx.locale()).to.equal('en'); }); }); + + describe('buildSocksProxyUrl', () => { + // Guards a regression (#826) where socks5h:// (used with Tor to resolve + // DNS through the proxy) was treated as a bare host and got a scheme + // prepended twice, producing an invalid URL like socks5://socks5h://... + ['socks', 'socks4', 'socks5', 'socks5h'].forEach(scheme => { + it(`passes through URLs with the ${scheme}:// scheme unchanged`, () => { + const url = `${scheme}://localhost:9050`; + expect(buildSocksProxyUrl(url)).to.equal(url); + }); + }); + + it('is case-insensitive when matching the scheme', () => { + const url = 'SOCKS5H://localhost:9050'; + expect(buildSocksProxyUrl(url)).to.equal(url); + }); + + it('prepends socks5:// to a bare host with no scheme', () => { + expect(buildSocksProxyUrl('localhost:9050')).to.equal( + 'socks5://localhost:9050', + ); + }); + + it('prepends socks5:// to an unsupported scheme, such as socks4h', () => { + expect(buildSocksProxyUrl('socks4h://localhost:9050')).to.equal( + 'socks5://socks4h://localhost:9050', + ); + }); + }); }); diff --git a/util/index.ts b/util/index.ts index dc6ab966..e4eeb907 100644 --- a/util/index.ts +++ b/util/index.ts @@ -605,6 +605,12 @@ export const removeLightningPrefix = (invoice: string) => { return invoice; }; +export const buildSocksProxyUrl = (socksProxyHost: string) => { + return /^(?:socks|socks4|socks5|socks5h):\/\//i.test(socksProxyHost) + ? socksProxyHost + : `socks5://${socksProxyHost}`; +}; + const generateRandomImage = (nonce: string) => { // Import imageCache here to avoid circular dependency const { imageCache } = require('./imageCache');