mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/telegramdesktop/tdesktop
synced 2026-09-20 08:03:45 +08:00
Support new secret types for path-ed WEB proxies.
This commit is contained in:
@@ -134,7 +134,9 @@ using ProxyData = MTP::ProxyData;
|
|||||||
? "&pass=" + qthelp::url_encode(proxy.password) : "")
|
? "&pass=" + qthelp::url_encode(proxy.password) : "")
|
||||||
+ (((proxy.type == Type::Mtproto || proxy.type == Type::Web)
|
+ (((proxy.type == Type::Mtproto || proxy.type == Type::Web)
|
||||||
&& !proxy.password.isEmpty())
|
&& !proxy.password.isEmpty())
|
||||||
? "&secret=" + proxy.password : "");
|
? "&secret=" + ((proxy.type == Type::Web)
|
||||||
|
? MTP::EncodeWebProxyLinkSecret(proxy)
|
||||||
|
: proxy.password) : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] QString ProxyDataToLocalLink(const ProxyData &proxy) {
|
[[nodiscard]] QString ProxyDataToLocalLink(const ProxyData &proxy) {
|
||||||
@@ -347,6 +349,11 @@ void ShareProxy(
|
|||||||
} else if (type == ProxyData::Type::Mtproto || web) {
|
} else if (type == ProxyData::Type::Mtproto || web) {
|
||||||
proxy.password = fields.value(u"secret"_q);
|
proxy.password = fields.value(u"secret"_q);
|
||||||
proxy.password.replace('+', '-').replace('/', '_');
|
proxy.password.replace('+', '-').replace('/', '_');
|
||||||
|
if (web) {
|
||||||
|
proxy.password = MTP::DecodeWebProxyLinkSecret(
|
||||||
|
proxy.password,
|
||||||
|
!proxy.webBasePath().isEmpty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -193,6 +193,15 @@ namespace {
|
|||||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A base path needs a client that understands one, so a link that carries a
|
||||||
|
// path encodes its secret as base64url of this marker byte followed by the real
|
||||||
|
// secret. A client without path support decodes 17 bytes whose first byte is not
|
||||||
|
// the 0xDD of a padded secret, so it reports the link as an unsupported proxy
|
||||||
|
// type and asks the user to update, instead of accepting a pathless entry.
|
||||||
|
// 0xDD is the one byte that must never be used here: the older parser reads a
|
||||||
|
// 17-byte secret starting with it as an ordinary valid one.
|
||||||
|
constexpr auto kWebProxyLinkSecretMarker = uchar(0x70);
|
||||||
|
|
||||||
// The address field accepts a pasted URL, so the scheme is removed before the
|
// The address field accepts a pasted URL, so the scheme is removed before the
|
||||||
// address is parsed and only the canonical form is stored. `http://` is left in
|
// address is parsed and only the canonical form is stored. `http://` is left in
|
||||||
// place, and therefore rejected: HTTPS and port 443 are fixed for a WEB proxy.
|
// place, and therefore rejected: HTTPS and port 443 are fixed for a WEB proxy.
|
||||||
@@ -333,6 +342,12 @@ QString WebProxyBridgeCapability(const ProxyData &proxy) {
|
|||||||
== u"Proxy.Example.COM/App/"_q);
|
== u"Proxy.Example.COM/App/"_q);
|
||||||
Assert(StripWebProxyScheme(u"http://proxy.example.com"_q)
|
Assert(StripWebProxyScheme(u"http://proxy.example.com"_q)
|
||||||
== u"http://proxy.example.com"_q);
|
== u"http://proxy.example.com"_q);
|
||||||
|
const auto plainSecret = u"8561944064fc730cbfa4473562d8ec59"_q;
|
||||||
|
const auto markedSecret = u"cIVhlEBk_HMMv6RHNWLY7Fk"_q;
|
||||||
|
Assert(DecodeWebProxyLinkSecret(markedSecret, true) == plainSecret);
|
||||||
|
Assert(DecodeWebProxyLinkSecret(markedSecret, false) == plainSecret);
|
||||||
|
Assert(DecodeWebProxyLinkSecret(plainSecret, false) == plainSecret);
|
||||||
|
Assert(DecodeWebProxyLinkSecret(plainSecret, true).isEmpty());
|
||||||
Assert(NormalizeWebProxyBasePath(u"per%20cent"_q).isEmpty());
|
Assert(NormalizeWebProxyBasePath(u"per%20cent"_q).isEmpty());
|
||||||
Assert(NormalizeWebProxyBasePath(u"dot.ted"_q).isEmpty());
|
Assert(NormalizeWebProxyBasePath(u"dot.ted"_q).isEmpty());
|
||||||
Assert(NormalizeWebProxyBasePath(u".."_q).isEmpty());
|
Assert(NormalizeWebProxyBasePath(u".."_q).isEmpty());
|
||||||
@@ -377,6 +392,43 @@ QString WebProxyBridgeCapability(const ProxyData &proxy) {
|
|||||||
key);
|
key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString EncodeWebProxyLinkSecret(const ProxyData &proxy) {
|
||||||
|
Expects(proxy.type == ProxyData::Type::Web);
|
||||||
|
|
||||||
|
const auto secret = proxy.secretFromMtprotoPassword();
|
||||||
|
if (proxy.webBasePath().isEmpty() || secret.empty()) {
|
||||||
|
return proxy.password;
|
||||||
|
}
|
||||||
|
auto marked = QByteArray(1, char(kWebProxyLinkSecretMarker));
|
||||||
|
marked.append(
|
||||||
|
reinterpret_cast<const char*>(secret.data()),
|
||||||
|
int(secret.size()));
|
||||||
|
return QString::fromLatin1(marked.toBase64(
|
||||||
|
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DecodeWebProxyLinkSecret(const QString &value, bool hasBasePath) {
|
||||||
|
const auto decoded = QByteArray::fromBase64(
|
||||||
|
value.toLatin1(),
|
||||||
|
QByteArray::Base64UrlEncoding
|
||||||
|
| QByteArray::AbortOnBase64DecodingErrors);
|
||||||
|
|
||||||
|
// A canonical secret is 16 bytes, 17 starting with 0xDD, or 21+ starting
|
||||||
|
// with 0xEE, so a longer value behind this marker is never ambiguous.
|
||||||
|
const auto marked = (decoded.size() >= 17)
|
||||||
|
&& (uchar(decoded[0]) == kWebProxyLinkSecretMarker);
|
||||||
|
if (marked) {
|
||||||
|
return QString::fromLatin1(decoded.mid(1).toHex());
|
||||||
|
}
|
||||||
|
|
||||||
|
// An unmarked secret on a link that carries a base path is rejected rather
|
||||||
|
// than accepted: that is exactly the link a client without path support
|
||||||
|
// would take for a pathless proxy on an empty host, so the marked form is
|
||||||
|
// required once a path is present. A root link keeps the plain secret and
|
||||||
|
// still works in those clients.
|
||||||
|
return hasBasePath ? QString() : value;
|
||||||
|
}
|
||||||
|
|
||||||
QString WebProxyBridgePath(const ProxyData &proxy) {
|
QString WebProxyBridgePath(const ProxyData &proxy) {
|
||||||
Expects(proxy.type == ProxyData::Type::Web);
|
Expects(proxy.type == ProxyData::Type::Web);
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,10 @@ struct ProxyData {
|
|||||||
|
|
||||||
[[nodiscard]] QString NormalizeWebProxyHost(const QString &value);
|
[[nodiscard]] QString NormalizeWebProxyHost(const QString &value);
|
||||||
[[nodiscard]] QString WebProxyBridgeCapability(const ProxyData &proxy);
|
[[nodiscard]] QString WebProxyBridgeCapability(const ProxyData &proxy);
|
||||||
|
[[nodiscard]] QString EncodeWebProxyLinkSecret(const ProxyData &proxy);
|
||||||
|
[[nodiscard]] QString DecodeWebProxyLinkSecret(
|
||||||
|
const QString &value,
|
||||||
|
bool hasBasePath);
|
||||||
[[nodiscard]] QString WebProxyBridgePath(const ProxyData &proxy);
|
[[nodiscard]] QString WebProxyBridgePath(const ProxyData &proxy);
|
||||||
[[nodiscard]] QString WebProxyBridgeUrl(const ProxyData &proxy);
|
[[nodiscard]] QString WebProxyBridgeUrl(const ProxyData &proxy);
|
||||||
[[nodiscard]] ProxyData ToDirectIpProxy(
|
[[nodiscard]] ProxyData ToDirectIpProxy(
|
||||||
|
|||||||
@@ -552,10 +552,14 @@ tg://webproxy?server=<address>&secret=<secret>
|
|||||||
|
|
||||||
`<address>` is the percent-encoded `host` or `host/path`, so a base path travels in
|
`<address>` is the percent-encoded `host` or `host/path`, so a base path travels in
|
||||||
the same parameter as `proxy.example.com%2Fdobry-cola-super-app`. There is no
|
the same parameter as `proxy.example.com%2Fdobry-cola-super-app`. There is no
|
||||||
separate path parameter. Following either link shows the address and secret with
|
separate path parameter. A link that carries a path also encodes its secret as
|
||||||
one connect action. It does not check status or enable the proxy until that action
|
unpadded base64url of `0x70` plus the real secret, so a client without path
|
||||||
is invoked. Saved WEB entries can be shared as a public link or a direct-scheme QR
|
support reports an unsupported proxy type and asks the user to update, instead of
|
||||||
link.
|
normalizing the address to an empty host and offering to connect to it. A
|
||||||
|
root link keeps the plain secret and still works in those clients. Following
|
||||||
|
either link shows the address and secret with one connect action. It does not
|
||||||
|
check status or enable the proxy until that action is invoked. Saved WEB entries
|
||||||
|
can be shared as a public link or a direct-scheme QR link.
|
||||||
|
|
||||||
Application proxy changes configure/deconfigure the web transport before MTP
|
Application proxy changes configure/deconfigure the web transport before MTP
|
||||||
sessions restart. WEB follows the MTProxy path in `Session`, `SessionPrivate`, and
|
sessions restart. WEB follows the MTProxy path in `Session`, `SessionPrivate`, and
|
||||||
|
|||||||
Reference in New Issue
Block a user