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.

160 lines
4.2 KiB

  1. 'use strict'
  2. const express = require('express')
  3. const router = express.Router()
  4. const readChunk = require('read-chunk')
  5. const fileType = require('file-type')
  6. const Promise = require('bluebird')
  7. const fs = Promise.promisifyAll(require('fs-extra'))
  8. const path = require('path')
  9. const _ = require('lodash')
  10. const validPathRe = new RegExp('^(([a-z0-9/-]|' + appdata.regex.cjk.source + ')+\\.[a-z0-9]+)$')
  11. const validPathThumbsRe = new RegExp('^([a-z0-9]+\\.png)$')
  12. // ==========================================
  13. // SERVE UPLOADS FILES
  14. // ==========================================
  15. router.get('/t/*', (req, res, next) => {
  16. let fileName = req.params[0]
  17. if (!validPathThumbsRe.test(fileName)) {
  18. return res.sendStatus(404).end()
  19. }
  20. // todo: Authentication-based access
  21. res.sendFile(fileName, {
  22. root: lcdata.getThumbsPath(),
  23. dotfiles: 'deny'
  24. }, (err) => {
  25. if (err) {
  26. res.status(err.status).end()
  27. }
  28. })
  29. })
  30. router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
  31. let destFolder = _.chain(req.body.folder).trim().toLower().value()
  32. upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
  33. if (!destFolderPath) {
  34. res.json({ ok: false, msg: 'Invalid Folder' })
  35. return true
  36. }
  37. Promise.map(req.files, (f) => {
  38. let destFilename = ''
  39. let destFilePath = ''
  40. return lcdata.validateUploadsFilename(f.originalname, destFolder, true).then((fname) => {
  41. destFilename = fname
  42. destFilePath = path.resolve(destFolderPath, destFilename)
  43. return readChunk(f.path, 0, 262)
  44. }).then((buf) => {
  45. // -> Check MIME type by magic number
  46. let mimeInfo = fileType(buf)
  47. if (!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
  48. return Promise.reject(new Error('Invalid file type.'))
  49. }
  50. return true
  51. }).then(() => {
  52. // -> Move file to final destination
  53. return fs.moveAsync(f.path, destFilePath, { clobber: false })
  54. }).then(() => {
  55. return {
  56. ok: true,
  57. filename: destFilename,
  58. filesize: f.size
  59. }
  60. }).reflect()
  61. }, {concurrency: 3}).then((results) => {
  62. let uplResults = _.map(results, (r) => {
  63. if (r.isFulfilled()) {
  64. return r.value()
  65. } else {
  66. return {
  67. ok: false,
  68. msg: r.reason().message
  69. }
  70. }
  71. })
  72. res.json({ ok: true, results: uplResults })
  73. return true
  74. }).catch((err) => {
  75. res.json({ ok: false, msg: err.message })
  76. return true
  77. })
  78. })
  79. })
  80. router.post('/file', lcdata.uploadFileHandler, (req, res, next) => {
  81. let destFolder = _.chain(req.body.folder).trim().toLower().value()
  82. upl.validateUploadsFolder(destFolder).then((destFolderPath) => {
  83. if (!destFolderPath) {
  84. res.json({ ok: false, msg: 'Invalid Folder' })
  85. return true
  86. }
  87. Promise.map(req.files, (f) => {
  88. let destFilename = ''
  89. let destFilePath = ''
  90. return lcdata.validateUploadsFilename(f.originalname, destFolder, false).then((fname) => {
  91. destFilename = fname
  92. destFilePath = path.resolve(destFolderPath, destFilename)
  93. // -> Move file to final destination
  94. return fs.moveAsync(f.path, destFilePath, { clobber: false })
  95. }).then(() => {
  96. return {
  97. ok: true,
  98. filename: destFilename,
  99. filesize: f.size
  100. }
  101. }).reflect()
  102. }, {concurrency: 3}).then((results) => {
  103. let uplResults = _.map(results, (r) => {
  104. if (r.isFulfilled()) {
  105. return r.value()
  106. } else {
  107. return {
  108. ok: false,
  109. msg: r.reason().message
  110. }
  111. }
  112. })
  113. res.json({ ok: true, results: uplResults })
  114. return true
  115. }).catch((err) => {
  116. res.json({ ok: false, msg: err.message })
  117. return true
  118. })
  119. })
  120. })
  121. router.get('/*', (req, res, next) => {
  122. let fileName = req.params[0]
  123. if (!validPathRe.test(fileName)) {
  124. return res.sendStatus(404).end()
  125. }
  126. // todo: Authentication-based access
  127. res.sendFile(fileName, {
  128. root: git.getRepoPath() + '/uploads/',
  129. dotfiles: 'deny'
  130. }, (err) => {
  131. if (err) {
  132. res.status(err.status).end()
  133. }
  134. })
  135. })
  136. module.exports = router