{"record":{"id":"1f2e796c6f41c4ae","repo":"siyuan-note/siyuan","slug":"custom-emoji-file-must-not-be-empty","errorCode":null,"errorMessage":"custom emoji file must not be empty","messagePattern":"custom emoji file must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":363,"sourceCode":"\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\n\tswitch http.DetectContentType(data) {\n\tcase \"image/png\":\n\t\text = \".png\"\n\tcase \"image/jpeg\":\n\t\text = \".jpg\"\n\tcase \"image/gif\":\n\t\text = \".gif\"\n\tcase \"image/webp\":\n\t\text = \".webp\"\n\tdefault:\n\t\traster = false\n\t}\n\tif raster {\n\t\tconfig, _, decodeErr := image.DecodeConfig(bytes.NewReader(data))\n\t\tif decodeErr != nil || config.Width < 1 || config.Height < 1 || config.Width > 16384 || config.Height > 16384 ||","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/system.go#L345-L381","documentation":"normalizeCustomEmojiData rejects empty downloads before any decoding. If the fetched bytes slice has length 0 there is no image to validate or store, so the operation fails with 'custom emoji file must not be empty'.","triggerScenarios":"The downloaded response body was empty (0 bytes) with a 200 status — e.g. the server returned success with no payload, or upstream code passed a zero-length slice into normalizeCustomEmojiData.","commonSituations":"URL points at an empty placeholder file; server returns 200 with empty body for soft errors; redirect chain ends on an empty response.","solutions":["Check the URL actually serves image bytes (curl the URL and inspect the body)","Pick a different source URL for the emoji","Add an upstream check so empty responses are reported before normalization","Inspect server-side generation if you control the emoji host"],"exampleFix":"// before\nnormalize(data) // data may be empty\n// after\nif len(data) == 0 {\n    return errors.New(\"emoji download returned no bytes; check the URL\")\n}\nnormalize(data)","handlingStrategy":"validation","validationCode":"const buf = await (await fetch(url)).arrayBuffer();\nif (buf.byteLength === 0) throw new Error(\"URL returned an empty body; pick another source\");","typeGuard":null,"tryCatchPattern":"try {\n  await registerEmoji(data);\n} catch (e) {\n  if (String(e).includes(\"must not be empty\")) {\n    notifyUser(\"Downloaded file is empty; verify the URL serves image bytes\");\n  }\n}","preventionTips":["Check downloaded byte length before submitting","Curl the URL and inspect the body when in doubt","Avoid URLs that return soft-200 empty responses"],"tags":["validation","empty-input","image"],"backgroundTag":"empty-required-field","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"}