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:
+23
-3
@@ -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: 检测并剥离 BOM(UTF-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)
|
||||
|
||||
Reference in New Issue
Block a user