ConversionController.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import logger from '@overleaf/logger'
  2. import { expressify } from '@overleaf/promise-utils'
  3. import fs from 'node:fs/promises'
  4. import fsSync from 'node:fs'
  5. import ConversionManager from './ConversionManager.js'
  6. import ResourceWriter from './ResourceWriter.js'
  7. import RequestParser from './RequestParser.js'
  8. import { pipeline } from 'node:stream/promises'
  9. import Settings from '@overleaf/settings'
  10. import Path from 'node:path'
  11. const SUPPORTED_CONVERSION_TYPES = new Map([['docx', 'docx']])
  12. async function convertDocumentToLaTeX(req, res) {
  13. const { path } = req.file
  14. const conversionType = req.query.type
  15. if (!Settings.enablePandocConversions) {
  16. await fs.unlink(path).catch(() => {})
  17. return res.sendStatus(404)
  18. }
  19. if (!conversionType || !['docx', 'markdown'].includes(conversionType)) {
  20. await fs.unlink(path).catch(() => {})
  21. return res.sendStatus(400)
  22. }
  23. logger.debug({ path, conversionType }, 'received file for conversion')
  24. const conversionId = crypto.randomUUID()
  25. let zipPath
  26. try {
  27. zipPath = await ConversionManager.promises.convertToLaTeXWithLock(
  28. conversionId,
  29. path,
  30. conversionType
  31. )
  32. } finally {
  33. await fs.unlink(path).catch(() => {})
  34. }
  35. try {
  36. const zipStat = await fs.stat(zipPath)
  37. res.setHeader('Content-Length', zipStat.size)
  38. res.attachment('conversion.zip')
  39. res.setHeader('X-Content-Type-Options', 'nosniff')
  40. const readStream = fsSync.createReadStream(zipPath)
  41. await pipeline(readStream, res)
  42. } finally {
  43. await fs
  44. .rm(Path.dirname(zipPath), { recursive: true, force: true })
  45. .catch(() => {})
  46. }
  47. }
  48. async function convertProjectToDocument(req, res) {
  49. if (!Settings.enablePandocConversions) {
  50. return res.sendStatus(404)
  51. }
  52. const type = req.query.type
  53. const extension = SUPPORTED_CONVERSION_TYPES.get(type)
  54. if (!extension) {
  55. return res.sendStatus(400)
  56. }
  57. const request = await RequestParser.promises.parse(req.body)
  58. request.project_id = req.params.project_id
  59. request.user_id = req.params.user_id
  60. request.metricsOpts = {}
  61. const conversionId = crypto.randomUUID()
  62. const conversionDir = Path.join(Settings.path.compilesDir, conversionId)
  63. logger.debug(
  64. {
  65. projectId: request.project_id,
  66. userId: request.user_id,
  67. rootResourcePath: request.rootResourcePath,
  68. type,
  69. },
  70. 'syncing resources for project-to-document conversion'
  71. )
  72. try {
  73. await ResourceWriter.promises.syncResourcesToDisk(request, conversionDir)
  74. const documentPath =
  75. await ConversionManager.promises.convertLaTeXToDocumentInDirWithLock(
  76. conversionId,
  77. conversionDir,
  78. request.rootResourcePath,
  79. type,
  80. extension
  81. )
  82. const documentStat = await fs.stat(documentPath)
  83. res.setHeader('Content-Length', documentStat.size)
  84. res.attachment(`output.${extension}`)
  85. res.setHeader('X-Content-Type-Options', 'nosniff')
  86. const readStream = fsSync.createReadStream(documentPath)
  87. await pipeline(readStream, res)
  88. } finally {
  89. await fs.rm(conversionDir, { recursive: true, force: true }).catch(() => {})
  90. }
  91. }
  92. export default {
  93. convertDocumentToLaTeX: expressify(convertDocumentToLaTeX),
  94. convertProjectToDocument: expressify(convertProjectToDocument),
  95. }