diff --git a/src/base/search/searchpluginmanager.cpp b/src/base/search/searchpluginmanager.cpp index 7d5122ef3..d3f3b0297 100644 --- a/src/base/search/searchpluginmanager.cpp +++ b/src/base/search/searchpluginmanager.cpp @@ -409,11 +409,11 @@ Path SearchPluginManager::engineLocation() void SearchPluginManager::applyProxySettings() { - // Define environment variables for urllib in search engine plugins - + // for python `urllib`: https://docs.python.org/3/library/urllib.request.html#urllib.request.ProxyHandler const QString HTTP_PROXY = u"http_proxy"_s; const QString HTTPS_PROXY = u"https_proxy"_s; - const QString SOCKS_PROXY = u"sock_proxy"_s; + // for `helpers.setupSOCKSProxy()`: https://everything.curl.dev/usingcurl/proxies/socks.html + const QString SOCKS_PROXY = u"qbt_socks_proxy"_s; if (!Preferences::instance()->useProxyForGeneralPurposes()) { @@ -427,7 +427,6 @@ void SearchPluginManager::applyProxySettings() switch (proxyConfig.type) { case Net::ProxyType::None: - case Net::ProxyType::SOCKS4: // TODO: implement python code m_proxyEnv.remove(HTTP_PROXY); m_proxyEnv.remove(HTTPS_PROXY); m_proxyEnv.remove(SOCKS_PROXY); @@ -435,9 +434,12 @@ void SearchPluginManager::applyProxySettings() case Net::ProxyType::HTTP: { - const QString proxyURL = proxyConfig.authEnabled - ? u"http://%1:%2@%3:%4"_s.arg(proxyConfig.username, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port)) - : u"http://%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port)); + const QString credential = proxyConfig.authEnabled + ? (proxyConfig.username + u':' + proxyConfig.password + u'@') + : QString(); + const QString proxyURL = u"http://%1%2:%3"_s + .arg(credential, proxyConfig.ip, QString::number(proxyConfig.port)); + m_proxyEnv.insert(HTTP_PROXY, proxyURL); m_proxyEnv.insert(HTTPS_PROXY, proxyURL); m_proxyEnv.remove(SOCKS_PROXY); @@ -446,9 +448,25 @@ void SearchPluginManager::applyProxySettings() case Net::ProxyType::SOCKS5: { - const QString proxyURL = proxyConfig.authEnabled - ? u"%1:%2@%3:%4"_s.arg(proxyConfig.username, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port)) - : u"%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port)); + const QString scheme = proxyConfig.hostnameLookupEnabled ? u"socks5h"_s : u"socks5"_s; + const QString credential = proxyConfig.authEnabled + ? (proxyConfig.username + u':' + proxyConfig.password + u'@') + : QString(); + const QString proxyURL = u"%1://%2%3:%4"_s + .arg(scheme, credential, proxyConfig.ip, QString::number(proxyConfig.port)); + + m_proxyEnv.remove(HTTP_PROXY); + m_proxyEnv.remove(HTTPS_PROXY); + m_proxyEnv.insert(SOCKS_PROXY, proxyURL); + } + break; + + case Net::ProxyType::SOCKS4: + { + const QString scheme = proxyConfig.hostnameLookupEnabled ? u"socks4a"_s : u"socks4"_s; + const QString proxyURL = u"%1://%2:%3"_s + .arg(scheme, proxyConfig.ip, QString::number(proxyConfig.port)); + m_proxyEnv.remove(HTTP_PROXY); m_proxyEnv.remove(HTTPS_PROXY); m_proxyEnv.insert(SOCKS_PROXY, proxyURL); diff --git a/src/searchengine/nova3/helpers.py b/src/searchengine/nova3/helpers.py index 47db27bcc..85eec1541 100644 --- a/src/searchengine/nova3/helpers.py +++ b/src/searchengine/nova3/helpers.py @@ -1,4 +1,4 @@ -#VERSION: 1.51 +#VERSION: 1.52 # Author: # Christophe DUMEZ (chris@qbittorrent.org) @@ -32,19 +32,19 @@ import gzip import html import io import os -import re import socket import socks import ssl import sys import tempfile import urllib.error +import urllib.parse import urllib.request from collections.abc import Mapping from typing import Any, Optional -def getBrowserUserAgent() -> str: +def _getBrowserUserAgent() -> str: """ Disguise as browser to circumvent website blocking """ # Firefox release calendar @@ -60,17 +60,33 @@ def getBrowserUserAgent() -> str: return f"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:{nowVersion}.0) Gecko/20100101 Firefox/{nowVersion}.0" -headers: dict[str, Any] = {'User-Agent': getBrowserUserAgent()} +_headers: dict[str, Any] = {'User-Agent': _getBrowserUserAgent()} -# SOCKS5 Proxy support -if "sock_proxy" in os.environ and len(os.environ["sock_proxy"].strip()) > 0: - proxy_str = os.environ["sock_proxy"].strip() - m = re.match(r"^(?:(?P[^:]+):(?P[^@]+)@)?(?P[^:]+):(?P\w+)$", - proxy_str) - if m is not None: - socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, m.group('host'), - int(m.group('port')), True, m.group('username'), m.group('password')) - socket.socket = socks.socksocket # type: ignore[misc] + +def injectSOCKSProxySocket() -> None: + socksURL = os.environ.get("qbt_socks_proxy") + if socksURL is not None: + parts = urllib.parse.urlsplit(socksURL) + resolveHostname = (parts.scheme == "socks4a") or (parts.scheme == "socks5h") + if (parts.scheme == "socks4") or (parts.scheme == "socks4a"): + socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS4, parts.hostname, parts.port, resolveHostname) + socket.socket = socks.socksocket # type: ignore[misc] + elif (parts.scheme == "socks5") or (parts.scheme == "socks5h"): + socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, parts.hostname, parts.port, resolveHostname, parts.username, parts.password) + socket.socket = socks.socksocket # type: ignore[misc] + else: + # the following code provide backward compatibility for older qbt versions + # TODO: scheduled be removed with qbt >= 5.3 + legacySocksURL = os.environ.get("sock_proxy") + if legacySocksURL is not None: + legacySocksURL = f"socks5h://{legacySocksURL.strip()}" + parts = urllib.parse.urlsplit(legacySocksURL) + socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, parts.hostname, parts.port, True, parts.username, parts.password) + socket.socket = socks.socksocket # type: ignore[misc] + + +# monkey patching, run it now +injectSOCKSProxySocket() # This is only provided for backward compatibility, new code should not use it @@ -80,7 +96,7 @@ htmlentitydecode = html.unescape def retrieve_url(url: str, custom_headers: Mapping[str, Any] = {}, request_data: Optional[Any] = None, ssl_context: Optional[ssl.SSLContext] = None, unescape_html_entities: bool = True) -> str: """ Return the content of the url page as a string """ - request = urllib.request.Request(url, request_data, {**headers, **custom_headers}) + request = urllib.request.Request(url, request_data, {**_headers, **custom_headers}) try: response = urllib.request.urlopen(request, context=ssl_context) except urllib.error.URLError as errno: @@ -112,7 +128,7 @@ def download_file(url: str, referer: Optional[str] = None, ssl_context: Optional """ Download file at url and write it to a file, return the path to the file and the url """ # Download url - request = urllib.request.Request(url, headers=headers) + request = urllib.request.Request(url, headers=_headers) if referer is not None: request.add_header('referer', referer) response = urllib.request.urlopen(request, context=ssl_context)