{"record":{"id":"27012c04f9b390e9","repo":"siyuan-note/siyuan","slug":"unsupported-custom-emoji-image-format","errorCode":null,"errorMessage":"unsupported custom emoji image format","messagePattern":"unsupported custom emoji image format","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":392,"sourceCode":"\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 ||\n\t\t\tint64(config.Width)*int64(config.Height) > 100*1000*1000 {\n\t\t\treturn nil, \"\", fmt.Errorf(\"invalid custom emoji image\")\n\t\t}\n\t\treturn data, ext, nil\n\t}\n\n\tsanitizedSVG, sanitizeErr := util.SanitizeSVG(string(data))\n\tif sanitizeErr == nil {\n\t\treturn []byte(sanitizedSVG), \".svg\", nil\n\t}\n\treturn nil, \"\", fmt.Errorf(\"unsupported custom emoji image format\")\n}\n\nfunc normalizeCustomEmojiPath(name, ext string) (string, error) {\n\tname = strings.TrimSpace(strings.ReplaceAll(name, \"\\\\\", \"/\"))\n\tparts := strings.Split(name, \"/\")\n\tif len(parts) == 0 {\n\t\treturn \"\", fmt.Errorf(\"custom emoji name must not be empty\")\n\t}\n\n\tlastIndex := len(parts) - 1\n\tswitch strings.ToLower(filepath.Ext(parts[lastIndex])) {\n\tcase \".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\":\n\t\tparts[lastIndex] = strings.TrimSuffix(parts[lastIndex], filepath.Ext(parts[lastIndex]))\n\t}\n\tfor i, part := range parts {\n\t\tpart = strings.TrimSpace(part)\n\t\tif part == \"\" || part == \".\" || part == \"..\" {\n\t\t\treturn \"\", fmt.Errorf(\"invalid custom emoji name\")","sourceCodeStart":374,"sourceCodeEnd":410,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/system.go#L374-L410","documentation":"If the bytes are neither a recognized raster format (png/jpeg/gif/webp) nor a sanitizable SVG, normalizeCustomEmojiData gives up with 'unsupported custom emoji image format'. Only those image formats are accepted as custom emoji.","triggerScenarios":"Downloading a file whose content type is not image/png, image/jpeg, image/gif, image/webp, or valid SVG — e.g. BMP, TIFF, ICO, PDF, or text — or an SVG that fails SanitizeSVG.","commonSituations":"User pastes a URL to an .ico favicon or .bmp image; server returns HTML instead of an image; SVG containing script is rejected by the sanitizer.","solutions":["Convert the image to PNG (or WebP/JPEG/GIF) before using it as an emoji","Remove scripts/unsafe elements from the SVG so sanitization succeeds","Check the URL serves one of the supported formats (DetectContentType is content-based, extension is irrelevant)","Verify the server is not returning an HTML page"],"exampleFix":"// before\nhost(\"logo.bmp\")\n// after\nconvertToPNG(\"logo.bmp\", \"logo.png\") // PNG is always supported\nhost(\"logo.png\")","handlingStrategy":"fallback","validationCode":"const type = (await fetch(url)).headers.get(\"content-type\");\nconst ok = [\"image/png\",\"image/jpeg\",\"image/gif\",\"image/webp\",\"image/svg+xml\"].includes(type);\nif (!ok) throw new Error(`format ${type} unsupported; convert to PNG`);","typeGuard":null,"tryCatchPattern":"try {\n  await registerEmoji(data);\n} catch (e) {\n  if (String(e).includes(\"unsupported custom emoji image format\")) {\n    const png = await convertToPNG(data); // fallback conversion\n    return registerEmoji(png);\n  }\n  throw e;\n}","preventionTips":["Convert BMP/TIFF/ICO sources to PNG before use","Sanitize SVGs (no scripts) before submission","Check the served content type, not just the file extension"],"tags":["image","format","validation"],"backgroundTag":"unsupported-operation","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"}