{"record":{"id":"14843833cef67634","repo":"fish2018/pansou","slug":"s-w-148438","errorCode":null,"errorMessage":"[%s] 创建搜索请求失败: %w","messagePattern":"\\[(.+?)\\] 创建搜索请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/xys/xys.go","lineNumber":248,"sourceCode":"\t\tlastErr = err\n\t}\n\t\n\treturn nil, fmt.Errorf(\"[%s] 重试 %d 次后仍然失败: %w\", p.Name(), maxRetries, lastErr)\n}\n\n// executeSearch 执行搜索请求\nfunc (p *XysPlugin) executeSearch(client *http.Client, token, keyword string) ([]model.SearchResult, error) {\n\t// 构建搜索URL\n\tsearchURL := fmt.Sprintf(\"%s%s?DToken2=%s&requestID=undefined&mode=90002&stype=undefined&scope_content=0&wd=%s&uk=&page=1&limit=20&screen_filetype=\",\n\t\tBaseURL, SearchPath, token, url.QueryEscape(keyword))\n\n\t// 创建带超时的上下文\n\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建搜索请求失败: %w\", p.Name(), err)\n\t}\n\n\t// 设置完整的请求头\n\treq.Header.Set(\"User-Agent\", UserAgent)\n\treq.Header.Set(\"Accept\", \"application/json, text/plain, */*\")\n\treq.Header.Set(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\")\n\treq.Header.Set(\"Connection\", \"keep-alive\")\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Set(\"Referer\", BaseURL+\"/\")\n\treq.Header.Set(\"Origin\", BaseURL)\n\treq.Header.Set(\"X-Requested-With\", \"XMLHttpRequest\")\n\n\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/xys/xys.go#L230-L266","documentation":"executeSearch wraps the error from http.NewRequestWithContext when building the POST request to the search endpoint. As with the token request, method/URL/body are constructed from constants and the token/keyword, so failure indicates the composed searchURL is not a valid URL. searchImpl aborts before any network I/O.","triggerScenarios":"http.NewRequestWithContext(ctx, \"POST\", searchURL, nil) errors — the fmt.Sprintf-composed searchURL (BaseURL + query string with DToken2/keyword interpolated) is malformed, e.g. unescaped control characters in the keyword.","commonSituations":"Search keyword containing raw newlines/control characters injected into the query string; BaseURL constant misconfigured; token value containing characters that break the URL.","solutions":["Log the composed searchURL and validate with url.Parse before constructing the request","URL-escape the keyword (url.QueryEscape) and the token before interpolating into the query string","Verify BaseURL and the search path format string in executeSearch are intact"],"exampleFix":"// before\nsearchURL := fmt.Sprintf(\"%s%s?DToken2=%s&...&wd=%s&...\", BaseURL, searchPath, token, keyword)\n// after\nsearchURL := fmt.Sprintf(\"%s%s?DToken2=%s&...&wd=%s&...\", BaseURL, searchPath, url.QueryEscape(token), url.QueryEscape(keyword))","handlingStrategy":"validation","validationCode":"u, err := url.Parse(searchURL)\nif err != nil || u.Host == \"\" {\n    return fmt.Errorf(\"invalid search URL: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n    return nil, fmt.Errorf(\"search request build failed: %w\", err)\n}","preventionTips":["Escape every interpolated value (keyword, token) with url.QueryEscape","Build query strings with url.Values.Encode() instead of raw Sprintf","Add a unit test composing searchURL with edge-case keywords (unicode, spaces, quotes)"],"tags":["http","request-construction","invalid-url","encoding"],"backgroundTag":"invalid-url-format","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}