You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wiki/server/core/asar.mjs

128 lines
3.3 KiB

import pickle from 'chromium-pickle-js'
import path from 'node:path'
import { UINT64 } from 'cuint'
import fs from 'node:fs'
/**
* Based of express-serve-asar (https://github.com/toyobayashi/express-serve-asar)
* by Fenglin Li (https://github.com/toyobayashi)
*/
export default {
fdCache: {},
async serve (pkgName, req, res, next) {
const packages = {
twemoji: path.join(WIKI.ROOTPATH, 'assets/svg/twemoji.asar')
}
const file = this.readFilesystemSync(packages[pkgName])
const { filesystem, fd } = file
const info = filesystem.getFile(req.path.substring(1))
if (info) {
res.set({
'Content-Type': 'image/svg+xml',
'Content-Length': info.size
})
fs.createReadStream('', {
fd,
autoClose: false,
start: 8 + filesystem.headerSize + parseInt(info.offset, 10),
end: 8 + filesystem.headerSize + parseInt(info.offset, 10) + info.size - 1
}).on('error', (err) => {
WIKI.logger.warn(err)
res.sendStatus(404)
}).pipe(res.status(200))
} else {
res.sendStatus(404)
}
},
async unload () {
const fds = Object.values(this.fdCache)
if (fds.length > 0) {
WIKI.logger.info('Closing ASAR file descriptors...')
const closeAsync = require('util').promisify(fs.close)
await Promise.all(fds.map(x => closeAsync(x.fd)))
this.fdCache = {}
}
},
readArchiveHeaderSync (fd) {
let size
let headerBuf
const sizeBuf = Buffer.alloc(8)
if (fs.readSync(fd, sizeBuf, 0, 8, null) !== 8) {
throw new Error('Unable to read header size')
}
const sizePickle = pickle.createFromBuffer(sizeBuf)
size = sizePickle.createIterator().readUInt32()
headerBuf = Buffer.alloc(size)
if (fs.readSync(fd, headerBuf, 0, size, null) !== size) {
throw new Error('Unable to read header')
}
const headerPickle = pickle.createFromBuffer(headerBuf)
const header = headerPickle.createIterator().readString()
return { header: JSON.parse(header), headerSize: size }
},
readFilesystemSync (archive) {
if (!this.fdCache[archive]) {
const fd = fs.openSync(archive, 'r')
const header = this.readArchiveHeaderSync(fd)
const filesystem = new Filesystem(archive)
filesystem.header = header.header
filesystem.headerSize = header.headerSize
this.fdCache[archive] = {
fd,
filesystem
}
}
return this.fdCache[archive]
}
}
class Filesystem {
constructor (src) {
this.src = path.resolve(src)
this.header = { files: {} }
this.offset = UINT64(0)
}
searchNodeFromDirectory (p) {
let json = this.header
const dirs = p.split(path.sep)
for (const dir of dirs) {
if (dir !== '.') {
json = json.files[dir]
}
}
return json
}
getNode (p) {
const node = this.searchNodeFromDirectory(path.dirname(p))
const name = path.basename(p)
if (name) {
return node.files[name]
} else {
return node
}
}
getFile (p, followLinks) {
followLinks = typeof followLinks === 'undefined' ? true : followLinks
const info = this.getNode(p)
if (!info) {
return false
}
if (info.link && followLinks) {
return this.getFile(info.link)
} else {
return info
}
}
}