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.

130 lines
3.2 KiB

  1. const pickle = require('chromium-pickle-js')
  2. const path = require('path')
  3. const UINT64 = require('cuint').UINT64
  4. const fs = require('fs')
  5. /* global WIKI */
  6. /**
  7. * Based of express-serve-asar (https://github.com/toyobayashi/express-serve-asar)
  8. * by Fenglin Li (https://github.com/toyobayashi)
  9. */
  10. const packages = {
  11. 'twemoji': path.join(WIKI.ROOTPATH, `assets/svg/twemoji.asar`)
  12. }
  13. module.exports = {
  14. fdCache: {},
  15. async serve (pkgName, req, res, next) {
  16. const file = this.readFilesystemSync(packages[pkgName])
  17. const { filesystem, fd } = file
  18. const info = filesystem.getFile(req.path.substring(1))
  19. if (info) {
  20. res.set({
  21. 'Content-Type': 'image/svg+xml',
  22. 'Content-Length': info.size
  23. })
  24. fs.createReadStream('', {
  25. fd,
  26. autoClose: false,
  27. start: 8 + filesystem.headerSize + parseInt(info.offset, 10),
  28. end: 8 + filesystem.headerSize + parseInt(info.offset, 10) + info.size - 1
  29. }).on('error', (err) => {
  30. WIKI.logger.warn(err)
  31. res.sendStatus(404)
  32. }).pipe(res.status(200))
  33. } else {
  34. res.sendStatus(404)
  35. }
  36. },
  37. async unload () {
  38. if (this.fdCache) {
  39. WIKI.logger.info('Closing ASAR file descriptors...')
  40. for (const fdItem in this.fdCache) {
  41. fs.closeSync(this.fdCache[fdItem].fd)
  42. }
  43. this.fdCache = {}
  44. }
  45. },
  46. readArchiveHeaderSync (fd) {
  47. let size
  48. let headerBuf
  49. const sizeBuf = Buffer.alloc(8)
  50. if (fs.readSync(fd, sizeBuf, 0, 8, null) !== 8) {
  51. throw new Error('Unable to read header size')
  52. }
  53. const sizePickle = pickle.createFromBuffer(sizeBuf)
  54. size = sizePickle.createIterator().readUInt32()
  55. headerBuf = Buffer.alloc(size)
  56. if (fs.readSync(fd, headerBuf, 0, size, null) !== size) {
  57. throw new Error('Unable to read header')
  58. }
  59. const headerPickle = pickle.createFromBuffer(headerBuf)
  60. const header = headerPickle.createIterator().readString()
  61. return { header: JSON.parse(header), headerSize: size }
  62. },
  63. readFilesystemSync (archive) {
  64. if (!this.fdCache[archive]) {
  65. const fd = fs.openSync(archive, 'r')
  66. const header = this.readArchiveHeaderSync(fd)
  67. const filesystem = new Filesystem(archive)
  68. filesystem.header = header.header
  69. filesystem.headerSize = header.headerSize
  70. this.fdCache[archive] = {
  71. fd,
  72. filesystem: filesystem
  73. }
  74. }
  75. return this.fdCache[archive]
  76. }
  77. }
  78. class Filesystem {
  79. constructor (src) {
  80. this.src = path.resolve(src)
  81. this.header = { files: {} }
  82. this.offset = UINT64(0)
  83. }
  84. searchNodeFromDirectory (p) {
  85. let json = this.header
  86. const dirs = p.split(path.sep)
  87. for (const dir of dirs) {
  88. if (dir !== '.') {
  89. json = json.files[dir]
  90. }
  91. }
  92. return json
  93. }
  94. getNode (p) {
  95. const node = this.searchNodeFromDirectory(path.dirname(p))
  96. const name = path.basename(p)
  97. if (name) {
  98. return node.files[name]
  99. } else {
  100. return node
  101. }
  102. }
  103. getFile (p, followLinks) {
  104. followLinks = typeof followLinks === 'undefined' ? true : followLinks
  105. const info = this.getNode(p)
  106. if (!info) {
  107. return false
  108. }
  109. if (info.link && followLinks) {
  110. return this.getFile(info.link)
  111. } else {
  112. return info
  113. }
  114. }
  115. }