{"record":{"id":"999103b4cef6c30a","repo":"siyuan-note/siyuan","slug":"invalid-asset-filename","errorCode":null,"errorMessage":"invalid asset filename","messagePattern":"invalid asset filename","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/upload.go","lineNumber":55,"sourceCode":"\t\"github.com/siyuan-note/siyuan/kernel/util\"\n)\n\n// InsertAssetBytes 将内存中的资源直接写入目标文档资源目录，避免生成内容经过明文临时文件。\nfunc InsertAssetBytes(id, fileName string, data []byte) (assetPath string, created bool, err error) {\n\tbt := treenode.GetBlockTree(id)\n\tif bt == nil {\n\t\treturn \"\", false, errors.New(Conf.Language(71))\n\t}\n\tif len(data) == 0 {\n\t\treturn \"\", false, errors.New(\"asset data is empty\")\n\t}\n\n\tbaseName := filepath.Base(fileName)\n\tfName := util.FilterUploadFileName(baseName)\n\text := strings.ToLower(filepath.Ext(fName))\n\tfName = strings.TrimSuffix(fName, filepath.Ext(fName)) + ext\n\tif fName == \"\" || fName == \".\" || ext == \"\" {\n\t\treturn \"\", false, errors.New(\"invalid asset filename\")\n\t}\n\n\tdocDirLocalPath := filepath.Join(util.DataDir, bt.BoxID, path.Dir(bt.Path))\n\tassetsDirPath := getAssetsDir(filepath.Join(util.DataDir, bt.BoxID), docDirLocalPath)\n\tif err = os.MkdirAll(assetsDirPath, 0755); err != nil {\n\t\treturn \"\", false, err\n\t}\n\n\treader := bytes.NewReader(data)\n\thash, err := util.GetEtagByHandle(reader, int64(len(data)))\n\tif err != nil {\n\t\treturn \"\", false, err\n\t}\n\tif existAssetPath := GetAssetPathByHash(hash, bt.BoxID); existAssetPath != \"\" {\n\t\toriginalName := assetNameWithoutID(filepath.Base(existAssetPath))\n\t\tif strings.EqualFold(assetNameWithoutID(fName), originalName) {\n\t\t\treturn strings.TrimPrefix(existAssetPath, \"/\"), false, nil\n\t\t}","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/upload.go#L37-L73","documentation":"After sanitizing the file name (util.FilterUploadFileName) and normalizing the extension to lowercase, InsertAssetBytes requires a non-empty base name and a non-empty extension. If the sanitized name collapses to empty/'.' or the file has no extension, it refuses to build the asset path, because assets are addressed by name+extension and an extension-less name would produce ambiguous/broken asset references.","triggerScenarios":"Calling InsertAssetBytes with fileName that has no extension ('image', '.hidden' cases where ext resolves empty), a name made entirely of characters stripped by FilterUploadFileName (illegal/path characters), or an empty fileName string.","commonSituations":"Generating assets with a programmatic name like 'screenshot' and forgetting '.png'; temp-file names or names derived from URLs without decoding to a file name; names containing only reserved characters on the target OS.","solutions":["Always pass a file name with a proper extension, e.g. 'diagram.png' instead of 'diagram'","Inspect what FilterUploadFileName strips: pre-clean the name of characters it removes so a non-empty base survives","Default a sensible extension in the caller when the source has none (e.g. derive from MIME type)","Validate the name in the caller: non-empty base, non-empty '.'-prefixed ext before calling"],"exampleFix":"// before\nmodel.InsertAssetBytes(id, \"image\", data)\n// after\nname := \"image\"\nif filepath.Ext(name) == \"\" {\n    name += \".png\" // derive from actual MIME type when possible\n}\nmodel.InsertAssetBytes(id, name, data)","handlingStrategy":"validation","validationCode":"base := filepath.Base(fileName)\nif base == \"\" || base == \".\" || filepath.Ext(base) == \"\" {\n    return errors.New(\"asset file name must include an extension\")\n}","typeGuard":"func hasNameAndExt(name string) bool {\n    b := filepath.Base(name)\n    return b != \"\" && b != \".\" && filepath.Ext(b) != \"\"\n}","tryCatchPattern":"assetPath, _, err := model.InsertAssetBytes(id, fileName, data)\nif err != nil && strings.Contains(err.Error(), \"invalid asset filename\") {\n    fileName = ensureExtension(fileName) // e.g. append from MIME type, retry once\n}","preventionTips":["Always generate asset names with an extension ('screenshot.png', not 'screenshot')","Avoid characters stripped by FilterUploadFileName in programmatic names","Derive extensions from MIME type when the source name is unknown"],"tags":["validation","filename","asset"],"backgroundTag":"invalid-argument-format","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"}