This repository was archived by the owner on Feb 23, 2025. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 53
/
Copy pathindex.ts
183 lines (150 loc) · 5.4 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import { BusBoyFile, AssFile, AssUser } from 'ass';
import fs from 'fs-extra';
import bb from 'express-busboy';
import crypto from 'crypto';
import { Router } from 'express';
import { Readable } from 'stream';
import * as data from '../data.js';
import { log } from '../log.js';
import { App } from '../app.js';
import { random } from '../generators.js';
import { UserConfig } from '../UserConfig.js';
import { getFileS3, uploadFileS3 } from '../s3.js';
import { rateLimiterMiddleware } from '../ratelimit.js';
import { DBManager } from '../sql/database.js';
import { DEFAULT_EMBED, prepareEmbed } from '../embed.js';
const router = Router({ caseSensitive: true });
//@ts-ignore // Required since bb.extends expects express.Application, not a Router (but it still works)
bb.extend(router, {
upload: true,
restrictMultiple: true,
allowedPath: (url: string) => url === '/',
limits: {
fileSize: () => (UserConfig.ready ? UserConfig.config.maximumFileSize : 50) * 1000000 // MB
}
});
// Render or redirect
router.get('/', (req, res) => UserConfig.ready ? res.render('index', { version: App.pkgVersion }) : res.redirect('/setup'));
// Upload flow
router.post('/', rateLimiterMiddleware('upload', UserConfig.config?.rateLimit?.upload), async (req, res) => {
// Check user config
if (!UserConfig.ready) return res.status(500).type('text').send('Configuration missing!');
// Does the file actually exist
if (!req.files || !req.files['file']) return res.status(400).type('text').send('No file was provided!');
else log.debug('Upload request received', `Using ${UserConfig.config.s3 != null ? 'S3' : 'local'} storage`);
// Type-check the file data
const bbFile: BusBoyFile = req.files['file'];
// Prepare file move
const uploads = UserConfig.config.uploadsDir;
const timestamp = Date.now().toString();
const fileKey = `${timestamp}_${bbFile.filename}`;
const destination = `${uploads}${uploads.endsWith('/') ? '' : '/'}${fileKey}`;
// S3 configuration
const s3 = UserConfig.config.s3 != null ? UserConfig.config.s3 : false;
try {
// Get the file size
const size = (await fs.stat(bbFile.file)).size;
// Get the hash
const sha256 = crypto.createHash('sha256').update(await fs.readFile(bbFile.file)).digest('base64');
// * Move the file
if (!s3) await fs.move(bbFile.file, destination);
else await uploadFileS3(await fs.readFile(bbFile.file), fileKey, bbFile.mimetype, size, sha256);
// Build ass metadata
const assFile: AssFile = {
fakeid: random({ length: UserConfig.config.idSize }), // todo: more generators
size,
sha256,
fileKey,
timestamp,
mimetype: bbFile.mimetype,
filename: bbFile.filename,
uploader: '0', // todo: users
save: {},
};
// Set the save location
if (!s3) assFile.save.local = destination;
else {
// Using S3 doesn't move temp file, delete it now
await fs.rm(bbFile.file);
assFile.save.s3 = true;
}
// * Save metadata
data.put('files', assFile.fakeid, assFile);
log.debug('File saved to', !s3 ? assFile.save.local! : 'S3');
return res.type('json').send({ resource: `${req.ass.host}/${assFile.fakeid}` });
} catch (err) {
log.error('Failed to upload file', bbFile.filename);
console.error(err);
return res.status(500).send(err);
}
});
router.get('/:fakeId', async (req, res) => {
if (!UserConfig.ready) res.redirect('/setup');
// Get the ID
const fakeId = req.params.fakeId;
// Get the file metadata
let _data;
try { _data = await DBManager.get('assfiles', fakeId); }
catch (err) {
log.error('Failed to get', fakeId);
console.error(err);
return res.status(500).send();
}
if (!_data) return res.status(404).send();
else {
let meta = _data as AssFile;
let user = await DBManager.get('assusers', meta.uploader) as AssUser | undefined;
res.render("viewer", {
url: `/direct/${fakeId}`,
uploader: user?.username ?? 'unknown',
size: meta.size,
time: meta.timestamp,
embed: prepareEmbed({
title: UserConfig.config.embed?.title ?? DEFAULT_EMBED.title,
description: UserConfig.config.embed?.description ?? DEFAULT_EMBED.description,
sitename: UserConfig.config.embed?.sitename ?? DEFAULT_EMBED.sitename
}, user ?? {
admin: false,
files: [],
id: "",
meta: {},
password: "",
tokens: [],
username: "unknown"
}, meta)
});
}
});
router.get('/direct/:fakeId', async (req, res) => {
if (!UserConfig.ready) res.redirect('/setup');
// Get the ID
const fakeId = req.params.fakeId;
// Get the file metadata
let _data;
try { _data = await data.get('files', fakeId); }
catch (err) {
log.error('Failed to get', fakeId);
console.error(err);
return res.status(500).send();
}
if (!_data) return res.status(404).send();
else {
const meta = _data as AssFile;
// File data can come from either S3 or local filesystem
let output: Readable | NodeJS.ReadableStream;
// Try to retrieve the file
if (!!meta.save.s3) {
const file = await getFileS3(meta.fileKey);
if (!file.Body) return res.status(500).send('Unknown error');
output = file.Body as Readable;
} else output = fs.createReadStream(meta.save.local!);
// Configure response headers
res.type(meta.mimetype)
.header('Content-Disposition', `inline; filename="${meta.filename}"`)
.header('Cache-Control', 'public, max-age=31536000, immutable')
.header('Accept-Ranges', 'bytes');
// Send the file (thanks to https://stackoverflow.com/a/67373050)
output.pipe(res);
}
});
export { router };