mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-08-20 21:33:27 -07:00
Add SOCKS4/SOCKS4a proxy support to search engine
Pass 'Perform hostname lookup via proxy' setting along the way. Also add underline to variables and functions that are private to the python module. PR #22510.
This commit is contained in:
parent
abafbc0685
commit
3d73026ff2
2 changed files with 59 additions and 25 deletions
|
@ -409,11 +409,11 @@ Path SearchPluginManager::engineLocation()
|
||||||
|
|
||||||
void SearchPluginManager::applyProxySettings()
|
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 HTTP_PROXY = u"http_proxy"_s;
|
||||||
const QString HTTPS_PROXY = u"https_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())
|
if (!Preferences::instance()->useProxyForGeneralPurposes())
|
||||||
{
|
{
|
||||||
|
@ -427,7 +427,6 @@ void SearchPluginManager::applyProxySettings()
|
||||||
switch (proxyConfig.type)
|
switch (proxyConfig.type)
|
||||||
{
|
{
|
||||||
case Net::ProxyType::None:
|
case Net::ProxyType::None:
|
||||||
case Net::ProxyType::SOCKS4: // TODO: implement python code
|
|
||||||
m_proxyEnv.remove(HTTP_PROXY);
|
m_proxyEnv.remove(HTTP_PROXY);
|
||||||
m_proxyEnv.remove(HTTPS_PROXY);
|
m_proxyEnv.remove(HTTPS_PROXY);
|
||||||
m_proxyEnv.remove(SOCKS_PROXY);
|
m_proxyEnv.remove(SOCKS_PROXY);
|
||||||
|
@ -435,9 +434,12 @@ void SearchPluginManager::applyProxySettings()
|
||||||
|
|
||||||
case Net::ProxyType::HTTP:
|
case Net::ProxyType::HTTP:
|
||||||
{
|
{
|
||||||
const QString proxyURL = proxyConfig.authEnabled
|
const QString credential = proxyConfig.authEnabled
|
||||||
? u"http://%1:%2@%3:%4"_s.arg(proxyConfig.username, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port))
|
? (proxyConfig.username + u':' + proxyConfig.password + u'@')
|
||||||
: u"http://%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port));
|
: 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(HTTP_PROXY, proxyURL);
|
||||||
m_proxyEnv.insert(HTTPS_PROXY, proxyURL);
|
m_proxyEnv.insert(HTTPS_PROXY, proxyURL);
|
||||||
m_proxyEnv.remove(SOCKS_PROXY);
|
m_proxyEnv.remove(SOCKS_PROXY);
|
||||||
|
@ -446,9 +448,25 @@ void SearchPluginManager::applyProxySettings()
|
||||||
|
|
||||||
case Net::ProxyType::SOCKS5:
|
case Net::ProxyType::SOCKS5:
|
||||||
{
|
{
|
||||||
const QString proxyURL = proxyConfig.authEnabled
|
const QString scheme = proxyConfig.hostnameLookupEnabled ? u"socks5h"_s : u"socks5"_s;
|
||||||
? u"%1:%2@%3:%4"_s.arg(proxyConfig.username, proxyConfig.password, proxyConfig.ip, QString::number(proxyConfig.port))
|
const QString credential = proxyConfig.authEnabled
|
||||||
: u"%1:%2"_s.arg(proxyConfig.ip, QString::number(proxyConfig.port));
|
? (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(HTTP_PROXY);
|
||||||
m_proxyEnv.remove(HTTPS_PROXY);
|
m_proxyEnv.remove(HTTPS_PROXY);
|
||||||
m_proxyEnv.insert(SOCKS_PROXY, proxyURL);
|
m_proxyEnv.insert(SOCKS_PROXY, proxyURL);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#VERSION: 1.51
|
#VERSION: 1.52
|
||||||
|
|
||||||
# Author:
|
# Author:
|
||||||
# Christophe DUMEZ (chris@qbittorrent.org)
|
# Christophe DUMEZ (chris@qbittorrent.org)
|
||||||
|
@ -32,19 +32,19 @@ import gzip
|
||||||
import html
|
import html
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import socket
|
import socket
|
||||||
import socks
|
import socks
|
||||||
import ssl
|
import ssl
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import urllib.error
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
def getBrowserUserAgent() -> str:
|
def _getBrowserUserAgent() -> str:
|
||||||
""" Disguise as browser to circumvent website blocking """
|
""" Disguise as browser to circumvent website blocking """
|
||||||
|
|
||||||
# Firefox release calendar
|
# 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"
|
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:
|
def injectSOCKSProxySocket() -> None:
|
||||||
proxy_str = os.environ["sock_proxy"].strip()
|
socksURL = os.environ.get("qbt_socks_proxy")
|
||||||
m = re.match(r"^(?:(?P<username>[^:]+):(?P<password>[^@]+)@)?(?P<host>[^:]+):(?P<port>\w+)$",
|
if socksURL is not None:
|
||||||
proxy_str)
|
parts = urllib.parse.urlsplit(socksURL)
|
||||||
if m is not None:
|
resolveHostname = (parts.scheme == "socks4a") or (parts.scheme == "socks5h")
|
||||||
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, m.group('host'),
|
if (parts.scheme == "socks4") or (parts.scheme == "socks4a"):
|
||||||
int(m.group('port')), True, m.group('username'), m.group('password'))
|
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS4, parts.hostname, parts.port, resolveHostname)
|
||||||
socket.socket = socks.socksocket # type: ignore[misc]
|
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
|
# 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:
|
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 """
|
""" 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:
|
try:
|
||||||
response = urllib.request.urlopen(request, context=ssl_context)
|
response = urllib.request.urlopen(request, context=ssl_context)
|
||||||
except urllib.error.URLError as errno:
|
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 file at url and write it to a file, return the path to the file and the url """
|
||||||
|
|
||||||
# Download url
|
# Download url
|
||||||
request = urllib.request.Request(url, headers=headers)
|
request = urllib.request.Request(url, headers=_headers)
|
||||||
if referer is not None:
|
if referer is not None:
|
||||||
request.add_header('referer', referer)
|
request.add_header('referer', referer)
|
||||||
response = urllib.request.urlopen(request, context=ssl_context)
|
response = urllib.request.urlopen(request, context=ssl_context)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue