chore: bump version to 0.3.8

Code audit fixes:
- CRITICAL: reorder Markdown pipeline (fixImages before sanitize)
- CRITICAL: fix path prefix separator check
- BLOCKING: remove duplicate useEffect in Editor
- BLOCKING: skip onChange when content unchanged
- BLOCKING: optimize Sidebar re-render with useMemo
- HIGH: cleanup FileWatcher polling intervals
- HIGH: improve validatePath segment check
- HIGH: fix isExternalUpdate race with counter
- HIGH: add will-navigate / setWindowOpenHandler
- HIGH: explicit strip in sanitize schema
- MEDIUM: random temp file suffix instead of Date.now()
- MEDIUM/LOW: add IndexedDB error boundaries
- LOW: support UTF-16 BOM detection
This commit is contained in:
thzxx
2026-06-18 21:28:35 +08:00
parent dd78ff15a9
commit 7f070eb11d
13 changed files with 165 additions and 63 deletions
+23 -3
View File
@@ -1,5 +1,6 @@
import { readFile, stat, writeFile, rename, readdir, unlink, lstat, realpath } from 'fs/promises'
import { join, extname, dirname, basename } from 'path'
import { randomBytes } from 'crypto'
import type { ReadFileResult, SaveFileResult, FileNode } from '../shared/types'
import { MAX_FILE_SIZE, ALLOWED_EXTENSIONS, SKIP_DIRS } from '../shared/constants'
@@ -11,8 +12,25 @@ export async function readFileContent(filePath: string): Promise<ReadFileResult>
if (fileStat.size > MAX_FILE_SIZE) {
return { success: false, error: `文件过大(${(fileStat.size / 1024 / 1024).toFixed(1)} MB),暂不支持超过 20MB 的文件` }
}
let content = await readFile(filePath, 'utf-8')
if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
// L-01: 检测并剥离 BOMUTF-8 FEFF / UTF-16 LE FFFE / UTF-16 BE FEFF
const buffer = await readFile(filePath)
let content: string
if (buffer.length >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
// UTF-16 BE: swap bytes to LE 再解码
const swapped = Buffer.allocUnsafe(buffer.length)
buffer.copy(swapped)
swapped.swap16()
content = swapped.toString('utf-16le')
if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
} else if (buffer.length >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
// UTF-16 LE
content = buffer.toString('utf-16le')
if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
} else {
// UTF-8 (含 FEFF BOM 剥离)
content = buffer.toString('utf-8')
if (content.charCodeAt(0) === 0xFEFF) content = content.slice(1)
}
return { success: true, content }
} catch (err) {
return { success: false, error: (err as Error).message }
@@ -20,8 +38,10 @@ export async function readFileContent(filePath: string): Promise<ReadFileResult>
}
// C-02: 临时文件写到与目标相同目录(避免跨盘 rename 失败)
// M-01: 使用随机 hex 后缀替代 Date.now(),防止本地攻击者预创建 symlink
export async function saveFileContent(filePath: string, content: string): Promise<SaveFileResult> {
const tmpFile = join(dirname(filePath), `.marklite-tmp-${Date.now()}-${basename(filePath)}`)
const randomSuffix = randomBytes(8).toString('hex')
const tmpFile = join(dirname(filePath), `.marklite-tmp-${randomSuffix}-${basename(filePath)}`)
try {
await writeFile(tmpFile, content, 'utf-8')
await rename(tmpFile, filePath)