| 1 | import path from 'node:path'; |
| 2 | import fs from 'node:fs'; |
| 3 | |
| 4 | import express from 'express'; |
| 5 | import sanitize from 'sanitize-filename'; |
| 6 | import { sync as writeFileSyncAtomic } from 'write-file-atomic'; |
| 7 | |
| 8 | import { validateAssetFileName } from './assets.js'; |
| 9 | import { clientRelativePath } from '../util.js'; |
| 10 | |
| 11 | export const router = express.Router(); |
| 12 | |
| 13 | router.post('/sanitize-filename', async (request, response) => { |
| 14 | try { |
| 15 | const fileName = String(request.body.fileName); |
| 16 | if (!fileName) { |
| 17 | return response.status(400).send('No fileName specified'); |
| 18 | } |
| 19 | |
| 20 | const sanitizedFilename = sanitize(fileName); |
| 21 | return response.send({ fileName: sanitizedFilename }); |
| 22 | } catch (error) { |
| 23 | console.error(error); |
| 24 | return response.sendStatus(500); |
| 25 | } |
| 26 | }); |
| 27 | |
| 28 | router.post('/upload', async (request, response) => { |
| 29 | try { |
| 30 | if (!request.body.name) { |
| 31 | return response.status(400).send('No upload name specified'); |
| 32 | } |
| 33 | |
| 34 | if (!request.body.data) { |
| 35 | return response.status(400).send('No upload data specified'); |
| 36 | } |
| 37 | |
| 38 | // Validate filename |
| 39 | const validation = validateAssetFileName(request.body.name); |
| 40 | if (validation.error) |
| 41 | return response.status(400).send(validation.message); |
| 42 | |
| 43 | const pathToUpload = path.join(request.user.directories.files, request.body.name); |
| 44 | writeFileSyncAtomic(pathToUpload, request.body.data, 'base64'); |
| 45 | const url = clientRelativePath(request.user.directories.root, pathToUpload); |
| 46 | console.info(`Uploaded file: ${url} from ${request.user.profile.handle}`); |
| 47 | return response.send({ path: url }); |
| 48 | } catch (error) { |
| 49 | console.error(error); |
| 50 | return response.sendStatus(500); |
| 51 | } |
| 52 | }); |
| 53 | |
| 54 | router.post('/delete', async (request, response) => { |
| 55 | try { |
| 56 | if (!request.body.path) { |
| 57 | return response.status(400).send('No path specified'); |
| 58 | } |
| 59 | |
| 60 | const pathToDelete = path.join(request.user.directories.root, request.body.path); |
| 61 | if (!pathToDelete.startsWith(request.user.directories.files)) { |
| 62 | return response.status(400).send('Invalid path'); |
| 63 | } |
| 64 | |
| 65 | if (!fs.existsSync(pathToDelete)) { |
| 66 | return response.status(404).send('File not found'); |
| 67 | } |
| 68 | |
| 69 | fs.unlinkSync(pathToDelete); |
| 70 | console.info(`Deleted file: ${request.body.path} from ${request.user.profile.handle}`); |
| 71 | return response.sendStatus(200); |
| 72 | } catch (error) { |
| 73 | console.error(error); |
| 74 | return response.sendStatus(500); |
| 75 | } |
| 76 | }); |
| 77 | |
| 78 | router.post('/verify', async (request, response) => { |
| 79 | try { |
| 80 | if (!Array.isArray(request.body.urls)) { |
| 81 | return response.status(400).send('No URLs specified'); |
| 82 | } |
| 83 | |
| 84 | const verified = {}; |
| 85 | |
| 86 | for (const url of request.body.urls) { |
| 87 | const pathToVerify = path.join(request.user.directories.root, url); |
| 88 | if (!pathToVerify.startsWith(request.user.directories.files)) { |
| 89 | console.warn(`File verification: Invalid path: ${pathToVerify}`); |
| 90 | continue; |
| 91 | } |
| 92 | const fileExists = fs.existsSync(pathToVerify); |
| 93 | verified[url] = fileExists; |
| 94 | } |
| 95 | |
| 96 | return response.send(verified); |
| 97 | } catch (error) { |
| 98 | console.error(error); |
| 99 | return response.sendStatus(500); |
| 100 | } |
| 101 | }); |