diff --git a/packages/insomnia/src/main/api.protocol.ts b/packages/insomnia/src/main/api.protocol.ts index ac239f6e69..828c6e49d2 100644 --- a/packages/insomnia/src/main/api.protocol.ts +++ b/packages/insomnia/src/main/api.protocol.ts @@ -2,12 +2,12 @@ import path from 'node:path'; import { Readable } from 'node:stream'; import { parse as urlParse } from 'node:url'; -import { Curl, CurlAuth, CurlFeature, CurlProxy, CurlSslOpt, type HeaderInfo } from '@getinsomnia/node-libcurl'; +import { Curl, CurlAuth, CurlFeature, CurlSslOpt, type HeaderInfo } from '@getinsomnia/node-libcurl'; import { app, net, protocol, session } from 'electron'; import { services } from 'insomnia-data'; import { getApiBaseURL } from '../common/constants'; -import { setDefaultProtocol, shouldBypassProxyForHost } from './network/libcurl-promise'; +import { parseResolvedProxy, setDefaultProtocol, shouldBypassProxyForHost } from './network/libcurl-promise'; import { resolveDbByKey } from './templating-worker-database'; export interface RegisterProtocolOptions { @@ -54,7 +54,12 @@ export async function registerInsomniaProtocols() { const settings = await services.settings.get(); // systemProxy follows the PAC return value format. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#return_value_format - let systemProxyStr = await session.defaultSession.resolveProxy(urlStr); + let systemProxyStr: string | undefined; + try { + systemProxyStr = await session.defaultSession.resolveProxy(urlStr); + } catch { + // If resolveProxy fails, fall back to direct connection + } // here we use libcurl to forward the SSE request because the SSE request sent by net.fetch can not be disconnected correctly in some cases // see https://github.com/electron/electron/issues/47097 @@ -68,64 +73,12 @@ export async function registerInsomniaProtocols() { if (!settings.proxyEnabled) { // follow system proxy - if (!systemProxyStr) { - // if systemProxy is empty, it means no proxy is used - systemProxyStr = 'DIRECT'; - } - - const proxy = systemProxyStr - .trim() - .split(/\s*;\s*/g) - .find(Boolean); - - // only the first proxy specified will be used - const firstProxy = proxy; - const parts = firstProxy?.split(/\s+/); - - const proxyType = parts?.[0]; - - if (proxyType === 'DIRECT') { - curl.setOpt(Curl.option.PROXY, ''); + const resolved = parseResolvedProxy(systemProxyStr); + if (resolved) { + curl.setOpt(Curl.option.PROXYTYPE, resolved.proxyType); + curl.setOpt(Curl.option.PROXY, resolved.proxyUrl); } else { - let unknownProxy = false; - let curlOptProxyType = CurlProxy.Http; - switch (proxyType) { - case 'PROXY': { - curlOptProxyType = CurlProxy.Http; - break; - } - case 'HTTP': { - curlOptProxyType = CurlProxy.Http; - break; - } - case 'SOCKS': { - curlOptProxyType = CurlProxy.Socks4; - break; - } - case 'HTTPS': { - curlOptProxyType = CurlProxy.Https; - break; - } - case 'SOCKS4': { - curlOptProxyType = CurlProxy.Socks4; - break; - } - case 'SOCKS5': { - curlOptProxyType = CurlProxy.Socks5; - break; - } - default: { - // unknown proxy type - unknownProxy = true; - break; - } - } - if (unknownProxy) { - curl.setOpt(Curl.option.PROXY, ''); - } else if (parts?.[1]) { - curl.setOpt(Curl.option.PROXYTYPE, curlOptProxyType); - curl.setOpt(Curl.option.PROXY, parts[1]); - } + curl.setOpt(Curl.option.PROXY, ''); } } else { const { protocol, hostname } = urlParse(urlStr); diff --git a/packages/insomnia/src/main/network/curl.ts b/packages/insomnia/src/main/network/curl.ts index 11d350805d..67d8d592c0 100644 --- a/packages/insomnia/src/main/network/curl.ts +++ b/packages/insomnia/src/main/network/curl.ts @@ -152,14 +152,15 @@ const openCurlConnection = async ( try { invariant(options.url, 'URL must be defined'); invariant(!options.url.startsWith('file://'), 'Local file URIs are not supported'); - + const readyStateChannel = `${protocolName}.${request._id}.${REALTIME_EVENTS_CHANNELS.READY_STATE}`; const settings = await services.settings.get(); const start = performance.now(); const clientCertificates = await services.clientCertificate.findByParentId(options.workspaceId); const filteredClientCertificates = filterClientCertificates(clientCertificates, options.url, 'https:'); - const { curl, debugTimeline } = createConfiguredCurlInstance({ + + const { curl, debugTimeline } = await createConfiguredCurlInstance({ req: { ...request, cookieJar: options.cookieJar, cookies: [], suppressUserAgent: options.suppressUserAgent }, finalUrl: options.url, settings, diff --git a/packages/insomnia/src/main/network/libcurl-promise.ts b/packages/insomnia/src/main/network/libcurl-promise.ts index a5534654ee..96e9f1a4a0 100644 --- a/packages/insomnia/src/main/network/libcurl-promise.ts +++ b/packages/insomnia/src/main/network/libcurl-promise.ts @@ -13,6 +13,7 @@ import { CurlHttpVersion, CurlInfoDebug, CurlNetrc, + CurlProxy, CurlSslOpt, } from '@getinsomnia/node-libcurl'; import { isValid } from 'date-fns'; @@ -123,7 +124,7 @@ export const curlRequest = (options: CurlRequestOptions) => invariant(!finalUrl.startsWith('file://'), 'Local file URIs are not supported'); const caCert = caCertficatePath && (await insecureReadFile(caCertficatePath)); - const { curl, debugTimeline } = createConfiguredCurlInstance({ + const { curl, debugTimeline } = await createConfiguredCurlInstance({ req, finalUrl, settings, @@ -286,7 +287,61 @@ export const curlRequest = (options: CurlRequestOptions) => } }); -export const createConfiguredCurlInstance = ({ +/** + * Parse a PAC-format proxy string (from Electron's session.resolveProxy) into + * a curl proxy URL and type. Returns null when DIRECT. + * Format: "PROXY host:port", "HTTPS host:port", "SOCKS host:port", etc. + * https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#return_value_format + */ +export function parseResolvedProxy(pacString: string | undefined): { proxyUrl: string; proxyType: CurlProxy } | null { + if (!pacString) { + return null; + } + // only the first proxy specified will be used + const proxy = pacString + .trim() + .split(/\s*;\s*/g) + .find(Boolean); + if (!proxy) { + return null; + } + const parts = proxy.split(/\s+/); + const proxyType = parts[0]; + if (proxyType === 'DIRECT') { + return null; + } + const proxyAddr = parts[1]; + if (!proxyAddr) { + return null; + } + let curlProxyType: CurlProxy; + switch (proxyType) { + case 'PROXY': + case 'HTTP': { + curlProxyType = CurlProxy.Http; + break; + } + case 'HTTPS': { + curlProxyType = CurlProxy.Https; + break; + } + case 'SOCKS': + case 'SOCKS4': { + curlProxyType = CurlProxy.Socks4; + break; + } + case 'SOCKS5': { + curlProxyType = CurlProxy.Socks5; + break; + } + default: { + return null; + } + } + return { proxyUrl: proxyAddr, proxyType: curlProxyType }; +} + +export const createConfiguredCurlInstance = async ({ req, finalUrl, settings, @@ -365,7 +420,21 @@ export const createConfiguredCurlInstance = ({ } if (!settings.proxyEnabled) { - curl.setOpt(Curl.option.PROXY, ''); + // When proxy is not explicitly configured, fall back to system proxy + let resolved: ReturnType = null; + try { + const systemProxy = await electron.session.defaultSession.resolveProxy(finalUrl); + resolved = parseResolvedProxy(systemProxy); + } catch { + // If resolveProxy fails (e.g. invalid URL, session issues), fall back to direct connection + } + if (resolved) { + curl.setOpt(Curl.option.PROXYTYPE, resolved.proxyType); + curl.setOpt(Curl.option.PROXY, resolved.proxyUrl); + debugTimeline.push({ value: `Using system proxy: ${resolved.proxyUrl}`, name: 'Text', timestamp: Date.now() }); + } else { + curl.setOpt(Curl.option.PROXY, ''); + } } else { const { protocol, hostname } = urlParse(req.url); const { httpProxy, httpsProxy, noProxy } = settings; diff --git a/packages/insomnia/src/main/proxy.ts b/packages/insomnia/src/main/proxy.ts index ee4aec6ed1..8cbd0e6d92 100644 --- a/packages/insomnia/src/main/proxy.ts +++ b/packages/insomnia/src/main/proxy.ts @@ -27,10 +27,10 @@ async function updateProxy() { } // Set proxy rules in the main session https://www.electronjs.org/docs/latest/api/structures/proxy-config - // no mode here — it overrides proxyRules ('system' ignores them) await session.defaultSession.setProxy({ proxyRules: proxyRules.join(';'), proxyBypassRules: noProxy ?? '', + mode: 'fixed_servers', }); return; } catch (err) {