chore: sync dev to master
Some checks failed
Build and Push Docker image / 🔧 Prepare Metadata (push) Has been cancelled
Build and Push Docker image / 🐳 Docker Build (${{ matrix.arch }}) (push) Has been cancelled
Build and Push Docker image / 🪟 Windows Build (amd64) (push) Has been cancelled
Build and Push Docker image / 🔗 Docker Manifest (push) Has been cancelled
Build and Push Docker image / 🚀 Release Artifacts (push) Has been cancelled

This commit is contained in:
github-actions[bot]
2026-06-06 05:22:11 +00:00
5 changed files with 183 additions and 27 deletions

View File

@@ -2,7 +2,7 @@ module github.com/aethersailor/subconverter-extended/bridge
go 1.25.5
require github.com/metacubex/mihomo v1.19.25
require github.com/metacubex/mihomo v1.19.26
require (
github.com/RyuaNerin/go-krypto v1.3.0 // indirect
@@ -24,7 +24,7 @@ require (
github.com/metacubex/randv2 v0.2.0 // indirect
github.com/metacubex/sing v0.5.7 // indirect
github.com/metacubex/sing-shadowsocks v0.2.12 // indirect
github.com/metacubex/tls v0.1.5 // indirect
github.com/metacubex/tls v0.1.6 // indirect
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
github.com/samber/lo v1.53.0 // indirect
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect

View File

@@ -21,8 +21,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0=
github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg=
github.com/enfein/mieru/v3 v3.31.0 h1:Fl2ocRCRXJzMygzdRjBHgqI996ZuIDHUmyQyovSf9sA=
github.com/enfein/mieru/v3 v3.31.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/enfein/mieru/v3 v3.33.0 h1:hv2jK8nqYHwpSG86U2rpZR2I8Aff1/J3ifRmd9NBbFc=
github.com/enfein/mieru/v3 v3.33.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
github.com/ericlagergren/polyval v0.0.0-20230805202542-18692a1b76f9 h1:NUmyvuwVoDsIFzOGFKW4zpCtQTbX2T4JpSn1jal64gM=
@@ -109,18 +109,18 @@ github.com/metacubex/hpke v0.1.0 h1:gu2jUNhraehWi0P/z5HX2md3d7L1FhPQE6/Q0E9r9xQ=
github.com/metacubex/hpke v0.1.0/go.mod h1:vfDm6gfgrwlXUxKDkWbcE44hXtmc1uxLDm2BcR11b3U=
github.com/metacubex/http v0.1.6 h1:xvXuvXMCMxCWMF5nEJF4yiKvXL+p2atWMzs37e80m1I=
github.com/metacubex/http v0.1.6/go.mod h1:Nxx0zZAo2AhRfanyL+fmmK6ACMtVsfpwIl1aFAik2Eg=
github.com/metacubex/jsonv2 v0.0.0-20260513175203-1c6abea7534c h1:KGhBHDe6FveU0ury+9RyX329nclM1CHODa0Fi+uOAYM=
github.com/metacubex/jsonv2 v0.0.0-20260513175203-1c6abea7534c/go.mod h1:F4sVXat6QjPXkNsKRDyyG3BhSkxPFFnRPEIwmmyCgbg=
github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df h1:S0vBzqjXok24VopstOgPd1JdgglW9tXehrqvwpQWbQ8=
github.com/metacubex/jsonv2 v0.0.0-20260518173308-f4597c22f1df/go.mod h1:F4sVXat6QjPXkNsKRDyyG3BhSkxPFFnRPEIwmmyCgbg=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604 h1:hJwCVlE3ojViC35MGHB+FBr8TuIf3BUFn2EQ1VIamsI=
github.com/metacubex/kcp-go v0.0.0-20260105040817-550693377604/go.mod h1:lpmN3m269b3V5jFCWtffqBLS4U3QQoIid9ugtO+OhVc=
github.com/metacubex/mihomo v1.19.25 h1:gVoiMx4jljDeGzfxXj3XlyY6clTk7npY+SGfZg8bBYQ=
github.com/metacubex/mihomo v1.19.25/go.mod h1:2wPHhdSY53InPrws2ZyzT1rPZgfy9eiag1aNsId1sZE=
github.com/metacubex/mihomo v1.19.26 h1:zTOrwEzgji2N6jFZwe6411hCbKmV1VGxVYZFKriX6uw=
github.com/metacubex/mihomo v1.19.26/go.mod h1:+L7tiesjMrF9I8lzTRsAFmHM9yh9RYdMEQP2tYnJqa8=
github.com/metacubex/mlkem v0.1.0 h1:wFClitonSFcmipzzQvax75beLQU+D7JuC+VK1RzSL8I=
github.com/metacubex/mlkem v0.1.0/go.mod h1:amhaXZVeYNShuy9BILcR7P0gbeo/QLZsnqCdL8U2PDQ=
github.com/metacubex/qpack v0.6.0 h1:YqClGIMOpiRYLjV1qOs483Od08MdPgRnHjt90FuaAKw=
github.com/metacubex/qpack v0.6.0/go.mod h1:lKGSi7Xk94IMvHGOmxS9eIei3bvIqpOAImEBsaOwTkA=
github.com/metacubex/quic-go v0.59.1-0.20260413153657-53bb22f2c306 h1:HlGLmLsWJMLSu0CMI9z/BmEnithB4oXM5Rom6/0Qxtg=
github.com/metacubex/quic-go v0.59.1-0.20260413153657-53bb22f2c306/go.mod h1:oNzMrmylS897M3zSMuapIdwSwfq6F2qW01Z3NhVRJhk=
github.com/metacubex/quic-go v0.59.1-0.20260520020949-fcd18c7b6ace h1:KXacx7dp1GYVMgxezwXRt5BMsEbvAYuA6rPFUmdAvcQ=
github.com/metacubex/quic-go v0.59.1-0.20260520020949-fcd18c7b6ace/go.mod h1:2YEQEvFrZ5V76oynMBDTlN+4fdnSHCa2uNJxv3cm1HU=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
@@ -129,30 +129,30 @@ github.com/metacubex/sing v0.5.7 h1:8OC+fhKFSv/l9ehEhJRaZZAOuthfZo68SteBVLe8QqM=
github.com/metacubex/sing v0.5.7/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
github.com/metacubex/sing-mux v0.3.9 h1:/aoBD2+sK2qsXDlNDe3hkR0GZuFDtwIZhOeGUx9W0Yk=
github.com/metacubex/sing-mux v0.3.9/go.mod h1:8bT7ZKT3clRrJjYc/x5CRYibC1TX/bK73a3r3+2E+Fc=
github.com/metacubex/sing-quic v0.0.0-20260512151354-8475655be853 h1:nZ5WNU6kjj6kBu4+2eMySFkUVGCop64rZnLMm+HPh8w=
github.com/metacubex/sing-quic v0.0.0-20260512151354-8475655be853/go.mod h1:6ayFGfzzBE85csgQkM3gf4neFq6s0losHlPRSxY+nuk=
github.com/metacubex/sing-quic v0.0.0-20260527143057-68e10a6afdc3 h1:PnMby5+kZXTl/CFDHfxMbMTaSRD+uMKMsrDYVQyAmX8=
github.com/metacubex/sing-quic v0.0.0-20260527143057-68e10a6afdc3/go.mod h1:6ayFGfzzBE85csgQkM3gf4neFq6s0losHlPRSxY+nuk=
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
github.com/metacubex/sing-shadowsocks2 v0.2.7 h1:hSuuc0YpsfiqYqt1o+fP4m34BQz4e6wVj3PPBVhor3A=
github.com/metacubex/sing-shadowsocks2 v0.2.7/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc h1:8wLoFfYQ88iGPL+krQ5tJsI8IAmkFjKpQL2q+y3pvss=
github.com/metacubex/sing-shadowtls v0.0.0-20260517015314-c11c36474edc/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
github.com/metacubex/sing-vmess v0.2.5 h1:m9Zt5I27lB9fmLMZfism9sH2LcnAfShZfwSkf6/KJoE=
github.com/metacubex/sing-vmess v0.2.5/go.mod h1:AwtlzUgf8COe9tRYAKqWZ+leDH7p5U98a0ZUpYehl8Q=
github.com/metacubex/sing-wireguard v0.0.0-20260507084707-690d479ec947 h1:IB03BvRQtvjWScyOK5jSQVJYY8osmZXHL+4VCEFMWcM=
github.com/metacubex/sing-wireguard v0.0.0-20260507084707-690d479ec947/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c h1:tH9FuQW357zp2xAGzkoZTGpNGMVmEFZov0iV5M2S5ew=
github.com/metacubex/sing-wireguard v0.0.0-20260520151737-7e7c7c1b854c/go.mod h1:eQZDJTx+IH3k4mXqaOJ3VJ9h9ZqOl60F7TLi5wAU51Q=
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141 h1:DK2l6m2Fc85H2BhiAPgbJygiWhesPlfGmF+9Vw6ARdk=
github.com/metacubex/smux v0.0.0-20260105030934-d0c8756d3141/go.mod h1:/yI4OiGOSn0SURhZdJF3CbtPg3nwK700bG8TZLMBvAg=
github.com/metacubex/ssh v0.1.0 h1:iGfr99qk/eMHzUnQ/0bTxXT8+8SWqLSHBWDHoAhngzw=
github.com/metacubex/ssh v0.1.0/go.mod h1:NUtl0d+/f2cG9ECEpMM8iCVOpmggQlC13oLeDUONDlU=
github.com/metacubex/tailscale v0.0.0-20260516120020-a21c2c99dcbe h1:ZdAKshacNruZGuKTE8WMTuxyGgpv/LySLoE/EEmgF9c=
github.com/metacubex/tailscale v0.0.0-20260516120020-a21c2c99dcbe/go.mod h1:2G1V82OGXgxT7m7046GA80I9SlcvczljCK0C7NQ3c10=
github.com/metacubex/tailscale-wireguard-go v0.0.0-20260513233728-8bc7ee255d04 h1:zk+mDDSBl5lv80WWtaFUbpj8XLb7AhjCUbn2pB37N0U=
github.com/metacubex/tailscale-wireguard-go v0.0.0-20260513233728-8bc7ee255d04/go.mod h1:pKUKBy7IcQ5r0i66gWENHgxKvBn8tlgAGx0DZMq8h5M=
github.com/metacubex/tailscale v0.0.0-20260520011538-f23132fac4b7 h1:LoJR4NMyNKHeEJoeGDtcsao7sV0NRkzMeV5H/0J0MIE=
github.com/metacubex/tailscale v0.0.0-20260520011538-f23132fac4b7/go.mod h1:MAo3HhE7968rIwmDvYTYE8xCsV4x+hLnkChdXeP3X4c=
github.com/metacubex/tailscale-wireguard-go v0.0.0-20260521124654-e1bf77ef79af h1:c60IbBMUq2h1M2m7+grMJJmBmrObxL8SwvNtm6Ozbwk=
github.com/metacubex/tailscale-wireguard-go v0.0.0-20260521124654-e1bf77ef79af/go.mod h1:i3zLKytWkOnyT1i9OmiLevWvrN5J5HE1+yjE7UYNfcQ=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443 h1:H6TnfM12tOoTizYE/qBHH3nEuibIelmHI+BVSxVJr8o=
github.com/metacubex/tfo-go v0.0.0-20251130171125-413e892ac443/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/tls v0.1.5 h1:ECcB83dj+zadnhlKcLnUUf1Sq6+vU0f/zoyU0+9oPTc=
github.com/metacubex/tls v0.1.5/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
github.com/metacubex/tls v0.1.6 h1:t2ubLneYa4ceyIC++54a57BLqZFA/QYUrhdjLk2GPwo=
github.com/metacubex/tls v0.1.6/go.mod h1:0XeVdL0cBw+8i5Hqy3lVeP9IyD/LFTq02ExvHM6rzEM=
github.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=
github.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
@@ -175,6 +175,8 @@ github.com/pires/go-proxyproto v0.8.0 h1:5unRmEAPbHXHuLjDg01CxJWf91cw3lKHc/0xzKp
github.com/pires/go-proxyproto v0.8.0/go.mod h1:iknsfgnH8EkjrMeMyvfKByp9TiBZCKZM0jx2xmKqnVY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e h1:dCWirM5F3wMY+cmRda/B1BiPsFtmzXqV9b0hLWtVBMs=
github.com/rasky/go-lzo v0.0.0-20200203143853-96a758eda86e/go.mod h1:9leZcVcItj6m9/CfHY5Em/iBrCz7js8LcRQGTKEEv2M=
github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=

View File

@@ -1643,6 +1643,8 @@ public:
using Expect100ContinueHandler =
std::function<int(const Request &, Response &)>;
using StartHandler = std::function<void()>;
using WebSocketHandler =
std::function<void(const Request &, ws::WebSocket &)>;
using SubProtocolSelector =
@@ -1694,6 +1696,9 @@ public:
Server &set_pre_request_handler(HandlerWithResponse handler);
Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
Server &set_start_handler(StartHandler handler);
Server &set_logger(Logger logger);
Server &set_pre_compression_logger(Logger logger);
Server &set_error_logger(ErrorLogger error_logger);
@@ -1883,6 +1888,7 @@ private:
Handler post_routing_handler_;
HandlerWithResponse pre_request_handler_;
Expect100ContinueHandler expect_100_continue_handler_;
StartHandler start_handler_;
mutable std::mutex logger_mutex_;
Logger logger_;
@@ -3842,6 +3848,7 @@ public:
void set_socket_options(SocketOptions socket_options);
void set_connection_timeout(time_t sec, time_t usec = 0);
void set_interface(const std::string &intf);
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
#ifdef CPPHTTPLIB_SSL_ENABLED
void set_ca_cert_path(const std::string &path);
@@ -3876,6 +3883,9 @@ private:
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
std::string interface_;
// Hostname-IP map
std::map<std::string, std::string> addr_map_;
#ifdef CPPHTTPLIB_SSL_ENABLED
bool is_ssl_ = false;
tls::ctx_t tls_ctx_ = nullptr;
@@ -4629,7 +4639,7 @@ inline std::string sha1(const std::string &input) {
// Pre-processing: adding padding bits
std::string msg = input;
uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
msg.push_back(static_cast<char>(0x80));
msg.push_back(static_cast<char>(0x80u));
while (msg.size() % 64 != 56) {
msg.push_back(0);
}
@@ -8443,6 +8453,14 @@ inline void coalesce_ranges(Ranges &ranges, size_t content_length) {
inline bool range_error(Request &req, Response &res) {
if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) {
req.ranges.clear();
if (res.status == StatusCode::PartialContent_206) {
res.status = StatusCode::OK_200;
}
return false;
}
ssize_t content_len = static_cast<ssize_t>(
res.content_length_ ? res.content_length_ : res.body.size());
@@ -11092,6 +11110,11 @@ Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) {
return *this;
}
inline Server &Server::set_start_handler(StartHandler handler) {
start_handler_ = std::move(handler);
return *this;
}
inline Server &Server::set_address_family(int family) {
address_family_ = family;
return *this;
@@ -11787,6 +11810,8 @@ inline bool Server::listen_internal() {
is_running_ = true;
auto se = detail::scope_exit([&]() { is_running_ = false; });
if (start_handler_) { start_handler_(); }
{
std::unique_ptr<TaskQueue> task_queue(new_task_queue());
@@ -20305,9 +20330,14 @@ inline bool WebSocketClient::connect() {
if (!is_valid_) { return false; }
shutdown_and_close();
// Check is custom IP specified for host_
std::string ip;
auto it = addr_map_.find(host_);
if (it != addr_map_.end()) { ip = it->second; }
Error error;
sock_ = detail::create_client_socket(
host_, std::string(), port_, address_family_, tcp_nodelay_, ipv6_v6only_,
host_, ip, port_, address_family_, tcp_nodelay_, ipv6_v6only_,
socket_options_, connection_timeout_sec_, connection_timeout_usec_,
read_timeout_sec_, read_timeout_usec_, write_timeout_sec_,
write_timeout_usec_, interface_, error);
@@ -20402,6 +20432,11 @@ inline void WebSocketClient::set_interface(const std::string &intf) {
interface_ = intf;
}
inline void WebSocketClient::set_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
addr_map_ = std::move(addr_map);
}
#ifdef CPPHTTPLIB_SSL_ENABLED
inline void WebSocketClient::set_ca_cert_path(const std::string &path) {

View File

@@ -12,6 +12,7 @@ from __future__ import annotations
import argparse
import difflib
import json
import re
import sys
import urllib.error
import urllib.parse
@@ -29,12 +30,22 @@ def build_url(base_url: str, path: str, params: dict[str, str] | None = None) ->
return f"{base}{path}" + (f"?{query}" if query else "")
def fetch(base_url: str, path: str, params: dict[str, str] | None, timeout: int) -> str:
def fetch_response(
base_url: str,
path: str,
params: dict[str, str] | None,
timeout: int,
headers: dict[str, str] | None = None,
) -> tuple[str, dict[str, str]]:
url = build_url(base_url, path, params)
request = urllib.request.Request(url, headers=headers or {})
try:
with urllib.request.urlopen(url, timeout=timeout) as response:
with urllib.request.urlopen(request, timeout=timeout) as response:
status = response.status
body = response.read().decode("utf-8", errors="replace")
response_headers = {
key.lower(): value for key, value in response.headers.items()
}
except urllib.error.HTTPError as exc:
body = exc.read().decode("utf-8", errors="replace")
raise AssertionError(f"{url} returned HTTP {exc.code}\n{body}") from exc
@@ -43,6 +54,11 @@ def fetch(base_url: str, path: str, params: dict[str, str] | None, timeout: int)
if status < 200 or status >= 300:
raise AssertionError(f"{url} returned HTTP {status}\n{body}")
return body, response_headers
def fetch(base_url: str, path: str, params: dict[str, str] | None, timeout: int) -> str:
body, _ = fetch_response(base_url, path, params, timeout)
return body
@@ -76,6 +92,75 @@ def run_checks(base_url: str, timeout: int, snapshot_dir: Path | None, update: b
if health.strip() != "ok":
raise AssertionError(f"/healthz returned unexpected body: {health!r}")
version_page, version_headers = fetch_response(
base_url, "/version", None, timeout
)
if (
"<!DOCTYPE html>" not in version_page
or "SubConverter-Extended" not in version_page
):
raise AssertionError("/version did not return the HTML version page")
if not version_headers.get("content-type", "").lower().startswith("text/html"):
raise AssertionError("/version HTML response has an unexpected content type")
navigation_page, navigation_headers = fetch_response(
base_url,
"/version",
None,
timeout,
{
"Origin": "https://edgetunnel.example",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Dest": "document",
},
)
if "<!DOCTYPE html>" not in navigation_page:
raise AssertionError("/version navigation request did not return HTML")
if not navigation_headers.get("content-type", "").lower().startswith(
"text/html"
):
raise AssertionError("/version navigation response has an unexpected content type")
probe_headers = {
"Origin": "https://edgetunnel.example",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Dest": "empty",
}
version_probe, version_probe_headers = fetch_response(
base_url, "/version", None, timeout, probe_headers
)
version_probe_line = version_probe.strip()
if not re.fullmatch(
r"SubConverter-Extended \S+ backend", version_probe_line
):
raise AssertionError(
f"/version probe returned an unexpected body: {version_probe!r}"
)
if "subconverter" not in version_probe_line.lower() or "<" in version_probe_line:
raise AssertionError("/version probe is not compatible with backend detection")
if not version_probe_headers.get("content-type", "").lower().startswith(
"text/plain"
):
raise AssertionError("/version probe response has an unexpected content type")
if version_probe_headers.get("access-control-allow-origin") != "*":
raise AssertionError("/version probe response is missing the CORS header")
if "no-store" not in version_probe_headers.get("cache-control", "").lower():
raise AssertionError("/version probe response is missing no-store caching")
vary = version_probe_headers.get("vary", "").lower()
for header in ("sec-fetch-mode", "sec-fetch-dest", "origin"):
if header not in vary:
raise AssertionError(f"/version probe Vary header is missing {header}")
legacy_probe, _ = fetch_response(
base_url,
"/version",
None,
timeout,
{"Origin": "https://edgetunnel.example"},
)
if legacy_probe != version_probe:
raise AssertionError("/version legacy browser probe response is inconsistent")
inspect_page = fetch(base_url, "/inspect", None, timeout)
if (
"Request Inspector" not in inspect_page

View File

@@ -3,6 +3,7 @@
#include <string>
#include "handler/settings.h"
#include "utils/string.h"
#include "version.h"
namespace {
@@ -88,6 +89,32 @@ std::string buildCommitLink(const std::string &build_id) {
build_id + "</a>";
}
std::string headerValue(const Request &request, const std::string &name) {
auto iter = request.headers.find(name);
if (iter == request.headers.end())
return "";
return trimWhitespace(iter->second, true, true);
}
bool isScriptVersionProbe(const Request &request) {
std::string fetch_mode = toLower(headerValue(request, "Sec-Fetch-Mode"));
std::string fetch_dest = toLower(headerValue(request, "Sec-Fetch-Dest"));
if (fetch_mode == "cors" && fetch_dest == "empty")
return true;
return fetch_mode.empty() && fetch_dest.empty() &&
!headerValue(request, "Origin").empty();
}
std::string buildPlainVersion() {
std::string version = VERSION;
std::string build_id = BUILD_ID;
if (!build_id.empty())
version += "-" + build_id;
return "SubConverter-Extended " + version + " backend\n";
}
} // namespace
namespace version_page {
@@ -102,9 +129,16 @@ std::string faviconLight(Request &, Response &response) {
return VERSION_FAVICON_LIGHT;
}
std::string page(Request &, Response &response) {
std::string page(Request &request, Response &response) {
response.headers["X-Robots-Tag"] =
"noindex, nofollow, noarchive, nosnippet, noimageindex";
response.headers["Vary"] = "Sec-Fetch-Mode, Sec-Fetch-Dest, Origin";
if (isScriptVersionProbe(request)) {
response.content_type = "text/plain; charset=utf-8";
response.headers["Cache-Control"] = "no-store";
return buildPlainVersion();
}
std::string build_id = BUILD_ID;
std::string build_date = BUILD_DATE;
std::string build_date_display = formatBuildDate(build_date);