{"record":{"id":"7f9697b0c2a85c42","repo":"siyuan-note/siyuan","slug":"download-custom-emoji-failed-with-status-d","errorCode":null,"errorMessage":"download custom emoji failed with status %d","messagePattern":"download custom emoji failed with status (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":340,"sourceCode":"\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}\n\treturn data, nil\n}\n\nfunc normalizeCustomEmojiData(data []byte) (normalized []byte, ext string, err error) {\n\tif len(data) == 0 {\n\t\treturn nil, \"\", fmt.Errorf(\"custom emoji file must not be empty\")\n\t}\n\n\traster := true","sourceCodeStart":322,"sourceCodeEnd":358,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/api/system.go#L322-L358","documentation":"Returned by downloadCustomEmojiData when the remote URL passed scheme/host validation but the HTTP GET returned a status other than 200 OK. The %d is filled with response.StatusCode. This is the catch-all for 4xx/5xx from the emoji host (404 missing file, 401/403 auth, 500 server error, etc.).","triggerScenarios":"POST /api/system/addCustomEmoji with the url form field (no file upload) where the resolved URL is reachable but returns non-200: a typo'd path on a CDN (404), a hotlink-protected host (403), or a temporarily down server (5xx).","commonSituations":"Pasting a stale image link whose host deleted the asset; hotlink protection on the source site; the URL points to an HTML login page (200 with HTML would instead fail at content-type detection, but a 302-to-login followed by 401 lands here); corporate proxy returning 502.","solutions":["Open the URL directly in a browser to read the actual status code and body the host returns.","If 404/410, switch to a URL that still hosts the image, or upload the file directly via the file form field instead of url.","If 401/403, host the emoji on a CDN/object-store that permits unauthenticated GET, or pre-download and upload as a file.","If 5xx, retry after the remote service recovers; do not treat it as a SiYuan bug."],"exampleFix":"// before\nemojiURL := \"https://cdn.example.com/old/icon.png\"\ndata, err := downloadCustomEmojiData(emojiURL) // 404 -> status error\n\n// after: host the asset on a reachable, unauthenticated URL\nemojiURL := \"https://my-bucket.s3.example.com/emojis/icon.png\"\n// or upload the file directly:\n//   POST /api/system/addCustomEmoji  form field: file=@icon.png","handlingStrategy":"try-catch","validationCode":"// Pre-check the URL is reachable with a HEAD before submitting to addCustomEmoji\nresp, err := http.Head(rawURL)\nif err != nil { return fmt.Errorf(\"emoji URL unreachable: %w\", err) }\nif resp.StatusCode != http.StatusOK { return fmt.Errorf(\"emoji host returned %d\", resp.StatusCode) }\nif ct := resp.Header.Get(\"Content-Type\"); !strings.HasPrefix(ct, \"image/\") {\n    return fmt.Errorf(\"emoji URL is not an image: %s\", ct)\n}","typeGuard":null,"tryCatchPattern":"data, err := downloadCustomEmojiData(rawURL)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed with status\") {\n        // remote-side error; surface the status to the user, do not retry 4xx\n        return fmt.Errorf(\"emoji source refused: %s\", err)\n    }\n    return err\n}","preventionTips":["Host emoji assets on a CDN/object store that permits anonymous GET and returns correct Content-Type.","Avoid hotlinking from sites with anti-leech protection.","When in doubt, upload the file directly via the file form field instead of the url field."],"tags":["network","http","emoji","download","api"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}