{"record":{"id":"ab900c3d1d716c24","repo":"siyuan-note/siyuan","slug":"invalid-asset-path-q","errorCode":null,"errorMessage":"invalid asset path: %q","messagePattern":"invalid asset path: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/model/asset_relink_references.go","lineNumber":40,"sourceCode":"\txhtml \"golang.org/x/net/html\"\n)\n\ntype assetRelinker struct {\n\toldPath     string\n\tnewPath     string\n\tresult      apicontract.AssetReferencesData\n\troutes      map[string]*assetRelinker\n\tcollectOnly bool\n\trecordless  bool\n\tdisabled    bool\n\tchanges     int\n}\n\n// relinkPath 按 URL 路径解码一次，查询参数和片段不参与资源文件名匹配。\nfunc relinkPath(value string) (string, error) {\n\tu, err := url.Parse(value)\n\tif err != nil || u.IsAbs() || u.Host != \"\" || u.Opaque != \"\" {\n\t\treturn \"\", fmt.Errorf(\"invalid asset path: %q\", value)\n\t}\n\tp := strings.TrimPrefix(u.Path, \"/\")\n\tif !strings.HasPrefix(p, \"assets/\") || path.Clean(p) != p || strings.ContainsAny(p, \"\\\\\\x00\\r\\n\") || strings.HasSuffix(p, \"/\") {\n\t\treturn \"\", fmt.Errorf(\"invalid asset path: %q\", value)\n\t}\n\tfor _, segment := range strings.Split(p, \"/\") {\n\t\tif strings.Contains(segment, \":\") {\n\t\t\treturn \"\", fmt.Errorf(\"invalid asset path: %q\", value)\n\t\t}\n\t}\n\treturn p, nil\n}\n\nfunc (r *assetRelinker) reference(raw, kind string, location apicontract.AssetReference) string {\n\tlink, annotationID := raw, \"\"\n\tif kind == \"annotation\" {\n\t\tlink, annotationID = util.SplitFileAnnotationRef(raw)\n\t\tif link == \"\" {","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/asset_relink_references.go#L22-L58","documentation":"relinkPath parses an asset URL reference before matching it to a file on disk. The first guard rejects values that fail url.Parse, are absolute URLs, or carry a host or opaque part — such references are not local workspace assets and cannot be relinked.","triggerScenarios":"Calling newAssetRelinker, reference, or list with an asset value like \"https://example.com/img.png\", \"mailto:a@b\", or a malformed value that url.Parse rejects.","commonSituations":"Documents containing external network images or protocol links pasted as asset references; clippings that stored absolute URLs; data migrated from other tools that use full URLs.","solutions":["Replace external URL references in the document with locally imported assets (assets/... paths)","Skip or preprocess non-local references before invoking the relinker","If the URL should be local, remove the scheme/host portion and ensure the value is a relative assets/ path"],"exampleFix":"// before\nrelink(\"https://example.com/img.png\")\n// after\nrelink(\"assets/img-20240101120000-ab12cd3.png\")","handlingStrategy":"validation","validationCode":"u, err := url.Parse(v)\nif err != nil || u.IsAbs() || u.Host != \"\" || u.Opaque != \"\" { /* not a local asset reference */ }","typeGuard":"func isRelativeAssetRef(v string) bool { u, err := url.Parse(v); return err == nil && !u.IsAbs() && u.Host == \"\" && u.Opaque == \"\" }","tryCatchPattern":"if _, err := relinkPath(v); err != nil { log.Warnf(\"skipping non-local ref %s: %v\", v, err); return \"\" }","preventionTips":["Import external images into assets/ instead of linking absolute URLs","Sanitize pasted/clipped content before storing asset references","Filter document asset references to relative paths before batch relinking"],"tags":["go","url","validation","assets"],"backgroundTag":"invalid-url-format","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}