{"record":{"id":"399a4cdb95799ab5","repo":"fish2018/pansou","slug":"s-w-399a4c","errorCode":null,"errorMessage":"[%s] 创建搜索请求失败: %w","messagePattern":"\\[(.+?)\\] 创建搜索请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/haitunsou/haitunsou.go","lineNumber":91,"sourceCode":"func (p *HaitunsouPlugin) SearchWithResult(keyword string, ext map[string]interface{}) (model.PluginSearchResult, error) {\n\treturn p.AsyncSearchWithResult(keyword, p.searchImpl, p.MainCacheKey, ext)\n}\n\nfunc (p *HaitunsouPlugin) searchImpl(client *http.Client, keyword string, _ map[string]interface{}) ([]model.SearchResult, error) {\n\tkeyword = cleanText(keyword)\n\tif keyword == \"\" {\n\t\treturn []model.SearchResult{}, nil\n\t}\n\tif client == nil {\n\t\tclient = http.DefaultClient\n\t}\n\n\tctx, cancel := context.WithTimeout(context.Background(), requestTimeout)\n\tdefer cancel()\n\tsearchURL := fmt.Sprintf(\"%s/s/%s.html\", strings.TrimRight(p.baseURL, \"/\"), url.PathEscape(keyword))\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建搜索请求失败: %w\", p.Name(), err)\n\t}\n\tsetRequestHeaders(req, p.baseURL)\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求返回 HTTP %d\", p.Name(), resp.StatusCode)\n\t}\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 读取搜索响应失败: %w\", p.Name(), err)\n\t}\n\tif len(body) > maxResponseSize {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索响应超过 %d 字节\", p.Name(), maxResponseSize)\n\t}","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/haitunsou/haitunsou.go#L73-L109","documentation":"This error is returned by HaitunsouPlugin.searchImpl when http.NewRequestWithContext fails to construct the GET request against the plugin's configured base URL plus the URL-escaped search keyword. In practice http.NewRequestWithContext only fails when the target URL fails url.Parse (invalid control characters, malformed URL), so it almost always indicates a bad baseURL configuration or a keyword containing characters that survived PathEscape and produced an unparseable URL. The original parse error is wrapped with %w for errors.Is/As inspection.","triggerScenarios":"Calling Search/SearchWithResult when p.baseURL is malformed (e.g. configured with a space, control character, or invalid scheme such as 'htp:/') so that fmt.Sprintf produces an unparseable URL, or when the cleaned keyword contains raw control bytes that PathEscape leaves invalid for a URL.","commonSituations":"Deployments overriding the default https://www.haitunsou.com base URL via config with a typo, trailing garbage, or an env var containing whitespace; unusual keywords with embedded newline/control characters after cleanText.","solutions":["Inspect the wrapped error with errors.As(*url.Error) to see the exact URL parse failure and fix the baseURL configuration value","Ensure baseURL comes from a validated source; validate with url.Parse(p.baseURL) at plugin construction time","Sanitize the keyword of control characters before searchImpl builds the URL","Fall back to the defaultBaseURL if the configured one fails validation"],"exampleFix":"// before\nsearchURL := fmt.Sprintf(\"%s/s/%s.html\", strings.TrimRight(p.baseURL, \"/\"), url.PathEscape(keyword))\n// after\nif _, err := url.Parse(p.baseURL); err != nil {\n    return nil, fmt.Errorf(\"[%s] invalid baseURL: %w\", p.Name(), err)\n}\nsearchURL := fmt.Sprintf(\"%s/s/%s.html\", strings.TrimRight(p.baseURL, \"/\"), url.PathEscape(keyword))","handlingStrategy":"validation","validationCode":"if _, err := url.Parse(strings.TrimSpace(p.baseURL)); err != nil {\n    return nil, fmt.Errorf(\"baseURL invalid: %w\", err)\n}\nif strings.ContainsFunc(keyword, func(r rune) bool { return r < 0x20 }) {\n    return nil, errors.New(\"keyword contains control characters\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := plugin.Search(kw, nil); err != nil {\n    var urlErr *url.Error\n    if errors.As(err, &urlErr) { /* bad URL: fix config, don't retry */ }\n}","preventionTips":["Validate baseURL with url.Parse at plugin construction","Never inject user input into a URL without PathEscape","Reject control characters in keywords before search","Pin the base URL to a tested constant unless config is validated"],"tags":["http","url-parsing","configuration"],"backgroundTag":"invalid-url","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"}