{"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":298,"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":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/util/custom_font.go#L280-L316","documentation":"Returned by detectCustomFontExtension when io.ReadFull cannot read 4 bytes from the font file header. It means the reader was exhausted, closed, or seeked past the end before the magic-bytes check. This fires before the TTF/OTF signature switch.","triggerScenarios":"InstallCustomFont (or loadCustomFontsLocked) calls detectCustomFontExtension on a font file that is shorter than 4 bytes, or on an *os.File whose position is already at EOF (e.g. it was read without seeking back to 0).","commonSituations":"A truncated upload that is at least 1 byte (so it passes the empty check at custom_font.go:103) but under 4 bytes; a code path that forgot fontFile.Seek(0, io.SeekStart) between reads; a file truncated on disk after stat but before read.","solutions":["Re-upload the font — the file is truncated or corrupt and cannot be parsed.","If you added a new code path that reads the font file, always Seek(0, io.SeekStart) before calling detectCustomFontExtension.","Confirm the upload handler streams the full body to the temp file and closes it cleanly."],"exampleFix":"// before\next, err := detectCustomFontExtension(fontFile)\n\n// after\nif _, err := fontFile.Seek(0, io.SeekStart); err != nil {\n    return err\n}\nif info, _ := fontFile.Stat(); info.Size() < 4 {\n    return errors.New(\"font file is truncated\")\n}\next, err := detectCustomFontExtension(fontFile)","handlingStrategy":"validation","validationCode":"func readableFontHeader(f *os.File) error {\n    if _, err := f.Seek(0, io.SeekStart); err != nil { return err }\n    info, _ := f.Stat()\n    if info.Size() < 4 { return errors.New(\"font file is truncated\") }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always Seek(0, io.SeekStart) before re-reading an *os.File in font code.","Reject uploads smaller than 4 bytes at the boundary."],"tags":["fonts","validation","parsing","filesystem"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}