mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 11:56:15 -08:00
59 lines
1.6 KiB
TypeScript
59 lines
1.6 KiB
TypeScript
import assert from 'node:assert/strict'
|
|
import crypto from 'node:crypto'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
|
|
export function sha256(text: string): string {
|
|
return crypto.createHash('sha256').update(text).digest('hex')
|
|
}
|
|
export function read(file: string): string {
|
|
return fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n')
|
|
}
|
|
|
|
export function ownedPath(root: string, relative: string): string {
|
|
assert(
|
|
relative
|
|
&& !relative.includes('\\')
|
|
&& !path.isAbsolute(relative)
|
|
&& !relative.split('/').includes('..'),
|
|
'Invalid documentation path',
|
|
)
|
|
const base = fs.realpathSync(root)
|
|
const file = path.resolve(base, relative)
|
|
let ancestor = file
|
|
while (true) {
|
|
try {
|
|
fs.lstatSync(ancestor)
|
|
break
|
|
}
|
|
catch (error) {
|
|
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
|
|
throw error
|
|
const parent = path.dirname(ancestor)
|
|
assert(parent !== ancestor, 'No existing documentation path ancestor')
|
|
ancestor = parent
|
|
}
|
|
}
|
|
const inside = path.relative(base, fs.realpathSync(ancestor))
|
|
assert(
|
|
inside !== '..'
|
|
&& !inside.startsWith(`..${path.sep}`)
|
|
&& !path.isAbsolute(inside),
|
|
'Documentation path escapes workspace',
|
|
)
|
|
return file
|
|
}
|
|
|
|
export function atomicWrite(file: string, content: string): void {
|
|
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
const temporary = `${file}.${crypto.randomUUID()}.tmp`
|
|
try {
|
|
fs.writeFileSync(temporary, content, { flag: 'wx' })
|
|
fs.renameSync(temporary, file)
|
|
}
|
|
finally {
|
|
if (fs.existsSync(temporary))
|
|
fs.unlinkSync(temporary)
|
|
}
|
|
}
|