{"record":{"id":"9f82a6b80a581d8c","repo":"siyuan-note/siyuan","slug":"url-has-no-host","errorCode":null,"errorMessage":"URL has no host","messagePattern":"URL has no host","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/httprequest.go","lineNumber":66,"sourceCode":"\t\t// 与 SSRFSafeDialer 共用 isPrivateIP，覆盖 NAT64、6to4、Teredo 等 IPv6 过渡地址。\n\t\tif isPrivateIP(ip) {\n\t\t\treturn errors.New(\"access to private/internal IP is prohibited\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// HTTPRequest 发起一次通用 HTTP 调用，供智能体 http_request 工具使用。\n// 与 WebFetch 不同：本函数不做 HTML→Markdown 转换，文本类响应（含 JSON/XML）原样返回，\n// 便于智能体直接消费 REST API 的 JSON 输出。method 取值：GET/POST/PUT/DELETE/PATCH。\n// 返回的 text 为响应正文（文本类）或落盘后的文件路径（二进制类）。\nfunc HTTPRequest(method, rawURL string, headers map[string]string, body string) (statusCode int, contentType string, text string, err error) {\n\tu, err := url.Parse(rawURL)\n\tif err != nil || (u.Scheme != \"http\" && u.Scheme != \"https\") {\n\t\treturn 0, \"\", \"\", errors.New(\"URL must start with http:// or https://\")\n\t}\n\tif u.Host == \"\" {\n\t\treturn 0, \"\", \"\", errors.New(\"URL has no host\")\n\t}\n\n\tif serr := CheckHostSSRF(u.Hostname()); serr != nil {\n\t\treturn 0, \"\", \"\", serr\n\t}\n\n\tmethod = strings.ToUpper(strings.TrimSpace(method))\n\tif method == \"\" {\n\t\tmethod = \"GET\"\n\t}\n\n\trequest := httpclient.NewBrowserRequest()\n\tfor k, v := range headers {\n\t\trequest.SetHeader(k, v)\n\t}\n\tif body != \"\" && method != \"GET\" && method != \"HEAD\" {\n\t\trequest.SetBody(body)\n\t}","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/httprequest.go#L48-L84","documentation":"Returned by HTTPRequest when url.Parse succeeds and the scheme is http/https, but u.Host is empty. This catches scheme-prefixed but hostless inputs like 'http:///path', 'https://?q=1', or 'http:path' that parse cleanly yet have no authority component.","triggerScenarios":"Calling util.HTTPRequest with a URL whose scheme is correct but which has no host — e.g. 'http:///foo', 'https://', 'http://?query', or a malformed relative URL that the parser treated as path-only.","commonSituations":"A URL built by joining 'https://' with an empty hostname; a copy-paste that lost the domain; an agent that stripped the host when reformatting a link.","solutions":["Ensure the URL has a non-empty host (e.g. 'https://example.com/path') before calling HTTPRequest.","If constructing URLs programmatically, assert u.Host != \"\" after url.Parse at the call site.","Reject and re-prompt the agent/user for a complete URL."],"exampleFix":"// before\nutil.HTTPRequest(\"GET\", \"http:///api/v1/status\", nil, \"\")\n\n// after\nutil.HTTPRequest(\"GET\", \"http://example.com/api/v1/status\", nil, \"\")","handlingStrategy":"validation","validationCode":"func urlHasHost(raw string) bool {\n    u, err := url.Parse(raw)\n    return err == nil && u.Host != \"\"\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["After url.Parse, assert u.Host != \"\" before passing to HTTPRequest.","Build URLs from (scheme, host, path) components rather than string concatenation."],"tags":["network","validation","url","http-request"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}