{"record":{"id":"62f913affda47d0f","repo":"siyuan-note/siyuan","slug":"font-file-is-invalid","errorCode":null,"errorMessage":"font file is invalid","messagePattern":"font file is invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/custom_font.go","lineNumber":303,"sourceCode":"\tentries, err := os.ReadDir(CustomFontDir())\n\tif err != nil {\n\t\treturn\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.IsDir() || !strings.HasPrefix(entry.Name(), \".font-\") {\n\t\t\tcontinue\n\t\t}\n\t\ttempPath := filepath.Clean(filepath.Join(CustomFontDir(), entry.Name()))\n\t\tif _, active := customFontTemps[tempPath]; !active {\n\t\t\t_ = os.Remove(tempPath)\n\t\t}\n\t}\n}\n\nfunc detectCustomFontExtension(reader io.Reader) (string, error) {\n\theader := make([]byte, 4)\n\tif _, err := io.ReadFull(reader, header); err != nil {\n\t\treturn \"\", errors.New(\"font file is invalid\")\n\t}\n\n\tswitch string(header) {\n\tcase \"\\x00\\x01\\x00\\x00\", \"true\":\n\t\treturn \".ttf\", nil\n\tcase \"OTTO\":\n\t\treturn \".otf\", nil\n\tdefault:\n\t\treturn \"\", errors.New(\"only TTF and OTF font files are supported\")\n\t}\n}\n\nfunc customFontHash(reader io.Reader) (string, error) {\n\thash := sha256.New()\n\tif _, err := io.Copy(hash, reader); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn hex.EncodeToString(hash.Sum(nil)), nil","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/util/custom_font.go#L285-L321","documentation":"detectCustomFontExtension sniffs the first 4 bytes of the font to detect its type. This error is thrown when reading the 4-byte magic header fails via io.ReadFull — the file is shorter than 4 bytes, unreadable, or an I/O error occurred, so the format cannot even be classified.","triggerScenarios":"Calling InstallCustomFont with a temp file of 1-3 bytes (passes the size>0 check but is too small to sniff); a read error on the underlying file; loadCustomFontsLocked encountering a truncated font on disk.","commonSituations":"Truncated uploads (connection cut mid-transfer); an empty-ish placeholder file a few bytes long; disk/IO errors on the fonts directory; a text file containing a single short line mistakenly uploaded as a font.","solutions":["Verify the file is at least 4 bytes and re-upload it completely","Check the upload pipeline for truncation (Content-Length, temp-file flush/close)","Compare local file size with the source font size to confirm the transfer completed","Re-export or re-download the font from a trusted source"],"exampleFix":"// before\nfont, _, err := util.InstallCustomFont(tempPath)\n// after\nif info, _ := os.Stat(tempPath); info != nil && info.Size() < 4 {\n    return fmt.Errorf(\"upload truncated (%d bytes)\", info.Size())\n}\nfont, _, err := util.InstallCustomFont(tempPath)","handlingStrategy":"validation","validationCode":"if info, err := os.Stat(tempPath); err == nil && info.Size() < 4 {\n    return fmt.Errorf(\"file too small to be a font: %d bytes\", info.Size())\n}","typeGuard":null,"tryCatchPattern":"font, _, err := util.InstallCustomFont(tempPath)\nif err != nil && err.Error() == \"font file is invalid\" {\n    // re-upload / verify transfer completeness\n}","preventionTips":["Verify uploaded size matches source size","Keep uploads binary-safe end to end","Reject obviously tiny files before staging"],"tags":["validation","font-magic-bytes","truncated-file"],"backgroundTag":"file-read-failed","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"}