diff --git a/client/main.go b/client/main.go index e689d89..76a7637 100644 --- a/client/main.go +++ b/client/main.go @@ -328,7 +328,7 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { return nil } - // Extract session token if redirect_uri present + // Extract session token var sessionToken string if RedirectURI != "" { if parsed, err := neturl.Parse(RedirectURI); err == nil { @@ -338,6 +338,12 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { return nil } } + // Fallback to top-level session_token field if not in redirect_uri + if sessionToken == "" { + if st, ok := errData["session_token"].(string); ok { + sessionToken = st + } + } // Extract is_sound_captcha_available isSound, ok := errData["is_sound_captcha_available"].(bool) @@ -2072,11 +2078,15 @@ func runVLESSMode(ctx context.Context, tp *turnParams, peer *net.UDPAddr, listen log.Panicf("TCP listen: %s", err) } +<<<<<<< HEAD wrappedListener, err := wrapISHListener(listener) if err != nil { log.Printf("Warning: failed to wrap listener: %v", err) wrappedListener = listener } +======= + wrappedListener := listener +>>>>>>> fix/vk-captcha-checkbox context.AfterFunc(ctx, func() { _ = wrappedListener.Close() }) log.Printf("VLESS mode: listening on %s (round-robin across %d sessions)", listenAddr, numSessions) diff --git a/client/manual_captcha.go b/client/manual_captcha.go index 4d40e2c..d41e572 100644 --- a/client/manual_captcha.go +++ b/client/manual_captcha.go @@ -14,6 +14,7 @@ import ( "net/http/httputil" neturl "net/url" "os/exec" + "regexp" "runtime" "strings" "time" @@ -164,11 +165,68 @@ func rewriteProxyCookies(header http.Header) { } } +// htmlURLAttrDoubleRe matches src/href/action attributes with double-quoted absolute or protocol-relative URLs. +var htmlURLAttrDoubleRe = regexp.MustCompile(`(?i)((?:src|href|action)\s*=\s*)"((?:https?:)?//[^"]+)"`) + +// htmlURLAttrSingleRe matches src/href/action attributes with single-quoted absolute or protocol-relative URLs. +var htmlURLAttrSingleRe = regexp.MustCompile(`(?i)((?:src|href|action)\s*=\s*)'((?:https?:)?//[^']+)'`) + +// rewriteHTMLAttrsServerSide rewrites absolute and protocol-relative URLs in src/href/action +// attributes of raw HTML. URLs matching the upstream origin are redirected to localhost; +// all other absolute URLs are routed through /generic_proxy so that cross-domain resources +// (st.vk.com, userapi.com, etc.) load correctly through the proxy. +func rewriteHTMLAttrsServerSide(html string, targetURL *neturl.URL) string { + localOrigin := localCaptchaOrigin() + upstreamOrigin := targetOrigin(targetURL) + + rewriteURL := func(rawURL string) string { + // Normalise protocol-relative URL to absolute using the upstream scheme + absURL := rawURL + if strings.HasPrefix(rawURL, "//") { + absURL = targetURL.Scheme + ":" + rawURL + } + if strings.HasPrefix(absURL, upstreamOrigin) { + return localOrigin + absURL[len(upstreamOrigin):] + } + // Already points to local proxy — leave as-is + if strings.HasPrefix(absURL, localOrigin) { + return rawURL + } + // Any other absolute URL → route through generic_proxy + return "/generic_proxy?proxy_url=" + neturl.QueryEscape(absURL) + } + + html = htmlURLAttrDoubleRe.ReplaceAllStringFunc(html, func(match string) string { + groups := htmlURLAttrDoubleRe.FindStringSubmatch(match) + if len(groups) < 3 { + return match + } + return groups[1] + `"` + rewriteURL(groups[2]) + `"` + }) + + html = htmlURLAttrSingleRe.ReplaceAllStringFunc(html, func(match string) string { + groups := htmlURLAttrSingleRe.FindStringSubmatch(match) + if len(groups) < 3 { + return match + } + return groups[1] + `'` + rewriteURL(groups[2]) + `'` + }) + + return html +} + func rewriteCaptchaHTML(html string, targetURL *neturl.URL) string { localOrigin := localCaptchaOrigin() upstreamOrigin := targetOrigin(targetURL) + + // Step 1: plain text replacement for the primary upstream origin html = strings.ReplaceAll(html, upstreamOrigin, localOrigin) + // Step 2: rewrite all other absolute URLs in HTML attributes server-side. + // This is critical: the browser begins downloading `, localOrigin, upstreamOrigin) + // Step 3: inject the client-side script as early as possible — at the opening
tag + // so that XHR/fetch overrides are active before any inline