diff --git a/src/background/handlers/onBeforeVideoWeaverRequest.ts b/src/background/handlers/onBeforeVideoWeaverRequest.ts index 9eba3c7..deec2f2 100644 --- a/src/background/handlers/onBeforeVideoWeaverRequest.ts +++ b/src/background/handlers/onBeforeVideoWeaverRequest.ts @@ -38,7 +38,7 @@ export default function onBeforeVideoWeaverRequest( textLower.includes("https://example.com") && textLower.includes("https://help.twitch.tv/"); const proxy = - details.proxyInfo && details.proxyInfo.type !== "direct" + details.proxyInfo && details.proxyInfo.type !== "DIRECT" ? getUrlFromProxyInfo(details.proxyInfo) : null; diff --git a/src/background/handlers/onProxyRequest.ts b/src/background/handlers/onProxyRequest.ts index 5dbaf5f..a27e60d 100644 --- a/src/background/handlers/onProxyRequest.ts +++ b/src/background/handlers/onProxyRequest.ts @@ -32,7 +32,7 @@ export default async function onProxyRequest( } const host = getHostFromUrl(details.url); - if (!host) return { type: "direct" }; + if (!host) return { type: "DIRECT" }; const documentHost = details.documentUrl ? getHostFromUrl(details.documentUrl) @@ -43,7 +43,7 @@ export default async function onProxyRequest( !passportHostRegex.test(documentHost) && // Passport requests have a `passport.twitch.tv` document URL. !twitchTvHostRegex.test(documentHost) ) { - return { type: "direct" }; + return { type: "DIRECT" }; } const proxies = store.state.optimizedProxiesEnabled @@ -92,12 +92,12 @@ export default async function onProxyRequest( if (proxyUsherRequest && usherHostRegex.test(host)) { if (details.url.includes("/vod/")) { console.log(`✋ '${details.url}' is a VOD manifest.`); - return { type: "direct" }; + return { type: "DIRECT" }; } const channelName = findChannelFromUsherUrl(details.url); if (isChannelWhitelisted(channelName)) { console.log(`✋ Channel '${channelName}' is whitelisted.`); - return { type: "direct" }; + return { type: "DIRECT" }; } console.log( `⌛ Proxying ${details.url} (${ @@ -119,7 +119,7 @@ export default async function onProxyRequest( findChannelFromTwitchTvUrl(tabUrl); if (isChannelWhitelisted(channelName)) { console.log(`✋ Channel '${channelName}' is whitelisted.`); - return { type: "direct" }; + return { type: "DIRECT" }; } console.log( `⌛ Proxying ${details.url} (${ @@ -149,12 +149,12 @@ export default async function onProxyRequest( return proxyInfoArray; } - return { type: "direct" }; + return { type: "DIRECT" }; } function getProxyInfoArrayFromUrls(urls: string[]): ProxyInfo[] { return [ ...urls.map(getProxyInfoFromUrl), - { type: "direct" } as ProxyInfo, // Fallback to direct connection if all proxies fail. + { type: "DIRECT" } as ProxyInfo, // Fallback to direct connection if all proxies fail. ]; } diff --git a/src/background/handlers/onResponseStarted.ts b/src/background/handlers/onResponseStarted.ts index a02b436..e188386 100644 --- a/src/background/handlers/onResponseStarted.ts +++ b/src/background/handlers/onResponseStarted.ts @@ -171,7 +171,7 @@ function getProxyFromDetails( return getUrlFromProxyInfo(possibleProxies[0]); } else { const proxyInfo = details.proxyInfo; // Firefox only. - if (!proxyInfo || proxyInfo.type === "direct") return null; + if (!proxyInfo || proxyInfo.type === "DIRECT") return null; return getUrlFromProxyInfo(proxyInfo); } } diff --git a/src/common/ts/proxyInfo.ts b/src/common/ts/proxyInfo.ts index a053a22..7f42552 100644 --- a/src/common/ts/proxyInfo.ts +++ b/src/common/ts/proxyInfo.ts @@ -1,9 +1,36 @@ import { Address6 } from "ip-address"; import type { ProxyInfo } from "../../types"; -export function getProxyInfoFromUrl( - url: string -): ProxyInfo & { type: "http"; host: string; port: number } { +export const proxySchemes = { + direct: "DIRECT", + http: "PROXY", + https: "HTTPS", + socks: "SOCKS", + socks4: "SOCKS4", + socks5: "SOCKS5", + quic: "QUIC", +} as const; + +type Protocol = keyof typeof proxySchemes; + +const defaultPorts: Partial<{ + [key in keyof typeof proxySchemes]: number; +}> = { + http: 80, + https: 443, + socks: 1080, + socks4: 1080, + socks5: 1080, + quic: 443, +}; + +export function getProxyInfoFromUrl(url: string) { + let protocol = ""; + if (url.includes("://")) { + const [_protocol, urlWithoutProtocol] = url.split("://"); + protocol = _protocol; + url = urlWithoutProtocol; + } const lastIndexOfAt = url.lastIndexOf("@"); const hostname = url.substring(lastIndexOfAt + 1, url.length); const lastIndexOfColon = getLastIndexOfColon(hostname); @@ -12,7 +39,9 @@ export function getProxyInfoFromUrl( let port: number | undefined = undefined; if (lastIndexOfColon === -1) { host = hostname; - port = 3128; // Default port + if (!protocol) { + port = 3128; // Default port + } } else { host = hostname.substring(0, lastIndexOfColon); port = Number(hostname.substring(lastIndexOfColon + 1, hostname.length)); @@ -30,8 +59,11 @@ export function getProxyInfoFromUrl( password = credentials.substring(indexOfColon + 1, credentials.length); } + port = port ? port : defaultPorts[protocol as Protocol]; + return { - type: "http", + type: proxySchemes[protocol as Protocol] ?? "PROXY", + protocol, host, port, username, diff --git a/src/common/ts/proxySettings.ts b/src/common/ts/proxySettings.ts index 2269791..d493f5f 100644 --- a/src/common/ts/proxySettings.ts +++ b/src/common/ts/proxySettings.ts @@ -92,7 +92,7 @@ function getProxyInfoStringFromUrls(urls: string[]): string { return [ ...urls.map(url => { const proxyInfo = getProxyInfoFromUrl(url); - return `PROXY ${getUrlFromProxyInfo({ + return `${proxyInfo.type} ${getUrlFromProxyInfo({ ...proxyInfo, // Don't include username/password in PAC script. username: undefined, diff --git a/src/options/options.ts b/src/options/options.ts index bd18c9e..c78a016 100644 --- a/src/options/options.ts +++ b/src/options/options.ts @@ -336,12 +336,16 @@ function isOptimizedProxyUrlAllowed(url: string): AllowedResult { return [false, "TTV LOL PRO v1 proxies are not compatible"]; } - if (/^https?:\/\//i.test(url)) { - return [false, "Proxy URLs must not contain a protocol (e.g. 'http://')"]; + const proxyInfo = getProxyInfoFromUrl(url); + if (proxyInfo.host.includes("/")) { + return [false, "Proxy URLs must not contain a path (e.g. '/path')"]; } - if (url.includes("/")) { - return [false, "Proxy URLs must not contain a path (e.g. '/path')"]; + try { + const host = url.substring(url.lastIndexOf("@") + 1, url.length); + new URL(`http://${host}`); // Throws if the host is invalid. + } catch { + return [false, `'${url}' is not a valid proxy URL`]; } try { diff --git a/src/types.ts b/src/types.ts index d642a20..86853f9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,9 +3,19 @@ export type KeyOfType = keyof { [P in keyof T as T[P] extends V ? P : never]: any; }; +// From https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md#proxy-server-schemes +export type ProxyScheme = + | "DIRECT" + | "PROXY" + | "HTTPS" + | "SOCKS" + | "SOCKS4" + | "SOCKS5" + | "QUIC"; + // From https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/proxy/ProxyInfo export interface ProxyInfo { - type: "direct" | "http" | "https" | "socks" | "socks4"; + type: ProxyScheme; host?: string; port?: number; username?: string;