{"record":{"id":"474a84308589e4cf","repo":"siyuan-note/siyuan","slug":"invalid-custom-emoji-url","errorCode":null,"errorMessage":"invalid custom emoji URL","messagePattern":"invalid custom emoji URL","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":339,"sourceCode":"\t\tfile, err := fileHeader.Open()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer file.Close()\n\t\treturn io.ReadAll(io.LimitReader(file, maxCustomEmojiSize+1))\n\t}\n\n\trawURL := strings.TrimSpace(request.URL)\n\tif rawURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"field [file] or [url] must not be empty\")\n\t}\n\treturn downloadCustomEmojiData(rawURL)\n}\n\nfunc downloadCustomEmojiData(rawURL string) ([]byte, error) {\n\tparsedURL, err := url.Parse(rawURL)\n\tif err != nil || (parsedURL.Scheme != \"http\" && parsedURL.Scheme != \"https\") || parsedURL.Host == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid custom emoji URL\")\n\t}\n\n\tresponse, err := util.NewCustomReqClient().R().Get(parsedURL.String())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"download custom emoji failed: %w\", err)\n\t}\n\tdefer response.Body.Close()\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"download custom emoji failed with status %d\", response.StatusCode)\n\t}\n\tif response.ContentLength > maxCustomEmojiSize {\n\t\treturn nil, fmt.Errorf(\"custom emoji file is too large\")\n\t}\n\n\tdata, err := io.ReadAll(io.LimitReader(response.Body, maxCustomEmojiSize+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read custom emoji response failed: %w\", err)\n\t}","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/system.go#L321-L357","documentation":"downloadCustomEmojiData parses the supplied emoji URL and requires an http or https scheme plus a non-empty host. Anything else (ftp:, data:, missing scheme, bare hostname) is rejected as \"invalid custom emoji URL\" before any network request is made.","triggerScenarios":"Passing a URL with a scheme other than http/https, a scheme-less string like \"example.com/a.png\", a malformed URL that url.Parse cannot handle, or a URL without a host.","commonSituations":"Users pasting \"www.example.com/img.png\" without the scheme; internal file:// or data: URIs; typos like \"htp://\"; URLs with stray whitespace or full-width characters.","solutions":["Prefix the URL with https:// (or http://) if the scheme is missing","Use only http/https URLs that include a host","Trim whitespace and fix typos in the scheme","Validate with new URL(rawURL) in JS or url.Parse in Go before calling the API"],"exampleFix":"// before\naddEmoji({ url: \"www.example.com/emoji.png\" }) // invalid custom emoji URL\n// after\naddEmoji({ url: \"https://www.example.com/emoji.png\" })","handlingStrategy":"validation","validationCode":"function isHttpUrl(s) {\n  try { const u = new URL(s.trim()); return u.protocol === \"http:\" || u.protocol === \"https:\"; } catch { return false; }\n}\nif (!isHttpUrl(rawUrl)) alert(\"Please enter a full http(s) URL\");","typeGuard":"function isHttpUrl(s) {\n  try { const u = new URL(s.trim()); return u.protocol === \"http:\" || u.protocol === \"https:\"; } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Auto-prepend https:// when the user omits the scheme","Trim whitespace and normalize full-width characters in URL inputs","Only accept http/https; reject data:, file:, and other schemes upfront"],"tags":["url","validation","emoji","download"],"backgroundTag":"invalid-url","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"}