diff --git a/.changeset/better-results-press.md b/.changeset/better-results-press.md new file mode 100644 index 0000000000000..c4e2d5b7d97ba --- /dev/null +++ b/.changeset/better-results-press.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": major +--- + +Removes `/ufs` legacy endpoint for downloading files diff --git a/apps/meteor/app/api/server/v1/rooms.ts b/apps/meteor/app/api/server/v1/rooms.ts index fa68fc305af83..b1d5d6ce2ccdd 100644 --- a/apps/meteor/app/api/server/v1/rooms.ts +++ b/apps/meteor/app/api/server/v1/rooms.ts @@ -307,8 +307,6 @@ API.v1.addRoute( const fileStore = FileUpload.getStore('Uploads'); const uploadedFile = await fileStore.insert(details, file.tempFilePath); - uploadedFile.path = FileUpload.getPath(`${uploadedFile._id}/${encodeURI(uploadedFile.name || '')}`); - await Uploads.updateFileComplete(uploadedFile._id, this.userId, omit(uploadedFile, '_id')); return API.v1.success({ diff --git a/apps/meteor/app/apps/server/converters/uploads.js b/apps/meteor/app/apps/server/converters/uploads.js index 60f85a8aa72f1..eaef30f43577e 100644 --- a/apps/meteor/app/apps/server/converters/uploads.js +++ b/apps/meteor/app/apps/server/converters/uploads.js @@ -1,6 +1,7 @@ import { Uploads } from '@rocket.chat/models'; import { transformMappedData } from './transformMappedData'; +import { getURL } from '../../../utils/server/getURL'; export class AppUploadsConverter { constructor(orch) { @@ -19,6 +20,19 @@ export class AppUploadsConverter { } const map = { + // `url`/`path` are no longer persisted on the upload; derive them from the file id+name, + // matching the canonical /file-upload route. Declared before `id`/`name` so the source + // fields are still present on the cloned data when these run. + url: (upload) => { + const relativePath = `/file-upload/${upload._id}/${encodeURIComponent(upload.name || '')}`; + delete upload.url; + return getURL(relativePath, { cdn: false, full: true }); + }, + path: (upload) => { + const relativePath = `/file-upload/${upload._id}/${encodeURIComponent(upload.name || '')}`; + delete upload.path; + return relativePath; + }, id: '_id', name: 'name', size: 'size', @@ -30,9 +44,7 @@ export class AppUploadsConverter { extension: 'extension', progress: 'progress', etag: 'etag', - path: 'path', token: 'token', - url: 'url', updatedAt: '_updatedAt', uploadedAt: 'uploadedAt', room: async (upload) => { diff --git a/apps/meteor/app/file-upload/server/config/GridFS.ts b/apps/meteor/app/file-upload/server/config/GridFS.ts index 94c6629e4f245..f932f8cdd45dc 100644 --- a/apps/meteor/app/file-upload/server/config/GridFS.ts +++ b/apps/meteor/app/file-upload/server/config/GridFS.ts @@ -149,9 +149,6 @@ FileUpload.configureUploadsStore('GridFS', 'GridFS:UserDataFiles', { collectionName: 'rocketchat_userDataFiles', }); -// DEPRECATED: backwards compatibility (remove) -UploadFS.getStores().rocketchat_uploads = UploadFS.getStores()['GridFS:Uploads']; - FileUpload.configureUploadsStore('GridFS', 'GridFS:Avatars', { collectionName: 'rocketchat_avatars', }); diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.spec.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.spec.ts index ddec9231b3bb2..46b5b1d2ac337 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.spec.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.spec.ts @@ -44,6 +44,7 @@ const { FileUpload, FileUploadClass } = proxyquire.noCallThru().load('./FileUplo '../../../../server/ufs/ufs-methods': sinon.stub(), '../../../settings/server': { settings: settingsStub }, '../../../utils/lib/mimeTypes': sinon.stub(), + '../../../utils/server/getURL': { getURL: sinon.stub() }, '../../../utils/server/lib/JWTHelper': { validateAndDecodeJWT: validateAndDecodeJWTStub, generateJWT: sinon.stub(), @@ -358,114 +359,4 @@ describe('FileUpload', () => { }); }); }); - - describe('getRequestUserId', () => { - it('should return undefined when no url is provided', async () => { - const request = { headers: {}, url: undefined } as any; - - const result = await FileUpload.getRequestUserId(request); - expect(result).to.be.undefined; - expect(usersModelStub.findOneByIdAndLoginToken.called).to.be.false; - }); - - it('should return undefined when no credentials are provided', async () => { - const request = { headers: {}, url: '/ufs/UserDataFiles/file-id' } as any; - - const result = await FileUpload.getRequestUserId(request); - expect(result).to.be.undefined; - expect(usersModelStub.findOneByIdAndLoginToken.called).to.be.false; - }); - - it('should return undefined when a uid is provided without a token', async () => { - const request = { headers: { 'x-user-id': 'user-1' }, url: '/ufs/UserDataFiles/file-id' } as any; - - const result = await FileUpload.getRequestUserId(request); - expect(result).to.be.undefined; - expect(usersModelStub.findOneByIdAndLoginToken.called).to.be.false; - }); - - it('should return undefined when the login token is invalid', async () => { - usersModelStub.findOneByIdAndLoginToken.resolves(null); - - const request = { headers: { 'x-user-id': 'user-1', 'x-auth-token': 'bad-token' }, url: '/ufs/UserDataFiles/file-id' } as any; - - const result = await FileUpload.getRequestUserId(request); - expect(result).to.be.undefined; - expect(usersModelStub.findOneByIdAndLoginToken.calledOnceWith('user-1', 'hashed_bad-token')).to.be.true; - }); - - it('should return the user id when credentials are valid via headers', async () => { - usersModelStub.findOneByIdAndLoginToken.resolves({ _id: 'user-1' }); - - const request = { headers: { 'x-user-id': 'user-1', 'x-auth-token': 'good-token' }, url: '/ufs/UserDataFiles/file-id' } as any; - - const result = await FileUpload.getRequestUserId(request); - expect(result).to.equal('user-1'); - expect(usersModelStub.findOneByIdAndLoginToken.calledOnceWith('user-1', 'hashed_good-token')).to.be.true; - }); - - it('should return the user id when credentials are valid via query string', async () => { - usersModelStub.findOneByIdAndLoginToken.resolves({ _id: 'user-1' }); - - const request = { headers: {}, url: '/ufs/UserDataFiles/file-id?rc_uid=user-1&rc_token=good-token' } as any; - - const result = await FileUpload.getRequestUserId(request); - expect(result).to.equal('user-1'); - expect(usersModelStub.findOneByIdAndLoginToken.calledOnceWith('user-1', 'hashed_good-token')).to.be.true; - }); - }); - - describe('UserDataFiles.onRead', () => { - // eslint-disable-next-line new-cap - const getOnRead = () => FileUpload.defaults.UserDataFiles().onRead; - - const createResponse = () => { - const res = { writeHead: sinon.stub(), setHeader: sinon.stub() }; - res.writeHead.returns(res); - return res as any; - }; - - it('should deny access to an unauthenticated request', async () => { - const res = createResponse(); - const file = { _id: 'file-id', userId: 'owner-1', name: 'export.zip' } as any; - const request = { headers: {}, url: '/ufs/UserDataFiles/file-id' } as any; - - const result = await getOnRead()('file-id', file, request, res); - expect(result).to.be.false; - expect(res.writeHead.calledOnceWith(403)).to.be.true; - expect(res.setHeader.called).to.be.false; - }); - - it('should deny access to an authenticated user who is not the owner', async () => { - usersModelStub.findOneByIdAndLoginToken.resolves({ _id: 'attacker-1' }); - - const res = createResponse(); - const file = { _id: 'file-id', userId: 'owner-1', name: 'export.zip' } as any; - const request = { - headers: { 'x-user-id': 'attacker-1', 'x-auth-token': 'attacker-token' }, - url: '/ufs/UserDataFiles/file-id', - } as any; - - const result = await getOnRead()('file-id', file, request, res); - expect(result).to.be.false; - expect(res.writeHead.calledOnceWith(403)).to.be.true; - expect(res.setHeader.called).to.be.false; - }); - - it('should allow access to the owner of the export', async () => { - usersModelStub.findOneByIdAndLoginToken.resolves({ _id: 'owner-1' }); - - const res = createResponse(); - const file = { _id: 'file-id', userId: 'owner-1', name: 'export.zip' } as any; - const request = { - headers: { 'x-user-id': 'owner-1', 'x-auth-token': 'owner-token' }, - url: '/ufs/UserDataFiles/file-id', - } as any; - - const result = await getOnRead()('file-id', file, request, res); - expect(result).to.be.true; - expect(res.writeHead.called).to.be.false; - expect(res.setHeader.calledOnceWith('content-disposition', 'attachment; filename="export.zip"')).to.be.true; - }); - }); }); diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index adc8ec70dd5e2..6f70b1ca594e5 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -35,6 +35,7 @@ import { MultipartUploadHandler } from '../../../api/server/lib/MultipartUploadH import { canAccessRoomAsync, canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; import { settings } from '../../../settings/server'; import { mime } from '../../../utils/lib/mimeTypes'; +import { getURL } from '../../../utils/server/getURL'; import { validateAndDecodeJWT, generateJWT } from '../../../utils/server/lib/JWTHelper'; import { fileUploadIsValidContentType } from '../../../utils/server/restrictions'; @@ -62,16 +63,6 @@ const defaults: Record Partial> = { return `${settings.get('uniqueID')}/uploads/${file.rid}/${file.userId}/${file._id}`; }, onValidate: FileUpload.uploadsOnValidate, - async onRead(_fileId: string, file: IUpload, req: http.IncomingMessage, res: http.ServerResponse) { - // Deprecated: Remove support to usf path - if (!(await FileUpload.requestCanAccessFiles(req, file))) { - res.writeHead(403); - return false; - } - - res.setHeader('content-disposition', `attachment; filename="${encodeURIComponent(file.name || '')}"`); - return true; - }, }; }, @@ -97,17 +88,6 @@ const defaults: Record Partial> = { return `${settings.get('uniqueID')}/uploads/userData/${file.userId}/${file._id}`; }, onValidate: FileUpload.uploadsOnValidate, - async onRead(_fileId: string, file: IUpload, req: http.IncomingMessage, res: http.ServerResponse) { - // UserDataFiles are GDPR data exports — only the owner of the export may download it. - const uid = await FileUpload.getRequestUserId(req); - if (!uid || uid !== file.userId) { - res.writeHead(403); - return false; - } - - res.setHeader('content-disposition', `attachment; filename="${encodeURIComponent(file.name || '')}"`); - return true; - }, }; }, }; @@ -450,31 +430,6 @@ export const FileUpload = { await Avatars.updateFileNameById(file._id, user.username); }, - async getRequestUserId({ headers = {}, url }: http.IncomingMessage): Promise { - if (!url) { - return undefined; - } - - const { query } = URL.parse(url, true); - // eslint-disable-next-line @typescript-eslint/naming-convention - let { rc_uid, rc_token } = query as Record; - - if (!rc_uid && headers.cookie) { - rc_uid = cookie.get('rc_uid', headers.cookie); - rc_token = cookie.get('rc_token', headers.cookie); - } - - const uid = rc_uid || (headers['x-user-id'] as string); - const authToken = rc_token || (headers['x-auth-token'] as string); - - if (!uid || !authToken) { - return undefined; - } - - const user = await Users.findOneByIdAndLoginToken(uid, hashLoginToken(authToken), { projection: { _id: 1 } }); - return user?._id; - }, - async requestCanAccessFiles({ headers = {}, url }: http.IncomingMessage, file?: IUpload) { if (!url || !settings.get('FileUpload_ProtectFiles')) { return true; @@ -908,12 +863,44 @@ export class FileUploadClass { throw new Error('Invalid file type'); } - return ufsComplete(fileId, this.name, { session: options?.session }); + const file = await ufsComplete(fileId, this.name, { session: options?.session }); + + // `/ufs` used to serve every store generically; it was replaced by per-store routes. + // Persist the store-aware public path/url so anything reading `IUpload.url`/`.path` + // (apps, integrations, …) keeps getting a link that resolves. + const path = this.getPublicPath(file); + if (path) { + const url = getURL(path, { cdn: false, full: true }); + await this.model.updateOne({ _id: file._id }, { $set: { path, url } }, { session: options?.session }); + file.path = path; + file.url = url; + } + + return file; } catch (e) { throw e; } } + // The modern route depends on which store the file lives in (the old `/ufs` path served all of them). + private getPublicPath(file: IUpload): string | undefined { + if (this.model === Uploads) { + return FileUpload.getPath(`${file._id}/${encodeURIComponent(file.name || '')}`); + } + if (this.model === UserDataFiles) { + return `/data-export/${file._id}`; + } + if (this.model === Avatars) { + if (file.rid) { + return `/avatar/room/${file.rid}`; + } + if (file.userId) { + return `/avatar/uid/${file.userId}`; + } + } + return undefined; + } + async insert( fileData: Omit, '_updatedAt'>, streamOrBuffer: stream.Readable | Buffer | string, diff --git a/apps/meteor/app/slackbridge/server/SlackAdapter.ts b/apps/meteor/app/slackbridge/server/SlackAdapter.ts index b02ad32ea13a2..451f413645ff0 100644 --- a/apps/meteor/app/slackbridge/server/SlackAdapter.ts +++ b/apps/meteor/app/slackbridge/server/SlackAdapter.ts @@ -1275,7 +1275,7 @@ export default class SlackAdapter { const file = await fileStore.insert(details, stream); - const url = file.url.replace(Meteor.absoluteUrl(), '/'); + const url = FileUpload.getPath(`${file._id}/${encodeURIComponent(file.name || '')}`); const attachment = { title: file.name, title_link: url, diff --git a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItemMenu.tsx b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItemMenu.tsx index d657e8f02750d..469df0ef14ed3 100644 --- a/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItemMenu.tsx +++ b/apps/meteor/client/views/room/contextualBar/RoomFiles/components/FileItemMenu.tsx @@ -65,11 +65,11 @@ const FileItemMenu = ({ rid, fileData, onClickDelete }: FileItemMenuProps) => { return; } - if (fileData.url && fileData.name) { + if (fileData.name) { const URL = window.webkitURL ?? window.URL; - const href = getURL(fileData.url); + const href = getURL(`/file-upload/${fileData._id}/${encodeURIComponent(fileData.name)}`); download(href, fileData.name); - URL.revokeObjectURL(fileData.url); + URL.revokeObjectURL(href); } }, disabled: !canDownloadFile, diff --git a/apps/meteor/server/ufs/ufs-config.ts b/apps/meteor/server/ufs/ufs-config.ts index cce931b97faa7..027e0d8e6958c 100644 --- a/apps/meteor/server/ufs/ufs-config.ts +++ b/apps/meteor/server/ufs/ufs-config.ts @@ -1,7 +1,6 @@ type ConfigOptions = { https?: boolean; simulateUploadSpeed?: number; - storesPath?: string; tmpDir?: string; tmpDirPermissions?: string; }; @@ -13,8 +12,6 @@ export class Config { public simulateUploadSpeed: RequiredConfigOptions['simulateUploadSpeed']; - public storesPath: RequiredConfigOptions['storesPath']; - public tmpDir: RequiredConfigOptions['tmpDir']; public tmpDirPermissions: RequiredConfigOptions['tmpDirPermissions']; @@ -24,7 +21,6 @@ export class Config { options = { https: false, simulateUploadSpeed: 0, - storesPath: 'ufs', tmpDir: '/tmp/ufs', tmpDirPermissions: '0700', ...options, @@ -37,9 +33,6 @@ export class Config { if (typeof options.simulateUploadSpeed !== 'number') { throw new TypeError('Config: simulateUploadSpeed is not a number'); } - if (typeof options.storesPath !== 'string') { - throw new TypeError('Config: storesPath is not a string'); - } if (typeof options.tmpDir !== 'string') { throw new TypeError('Config: tmpDir is not a string'); } @@ -49,7 +42,6 @@ export class Config { this.https = options.https; this.simulateUploadSpeed = options.simulateUploadSpeed; - this.storesPath = options.storesPath; this.tmpDir = options.tmpDir; this.tmpDirPermissions = options.tmpDirPermissions; } diff --git a/apps/meteor/server/ufs/ufs-server.ts b/apps/meteor/server/ufs/ufs-server.ts index 4b528a1a59ff3..b3a76a1d0676e 100644 --- a/apps/meteor/server/ufs/ufs-server.ts +++ b/apps/meteor/server/ufs/ufs-server.ts @@ -1,11 +1,6 @@ import fs from 'node:fs'; -import stream from 'node:stream'; -import { pipeline } from 'node:stream/promises'; -import URL from 'node:url'; -import zlib from 'node:zlib'; import { Meteor } from 'meteor/meteor'; -import { WebApp } from 'meteor/webapp'; import mkdirp from 'mkdirp'; import { UploadFS } from './ufs'; @@ -32,265 +27,3 @@ Meteor.startup(() => { } }); }); - -// Listen HTTP requests to serve files -WebApp.connectHandlers.use(async (req, res, next) => { - // Quick check to see if request should be caught - if (!req.url?.includes(`/${UploadFS.config.storesPath}/`)) { - next(); - return; - } - - // Remove store path - const parsedUrl = URL.parse(req.url, true); - const path = parsedUrl.pathname?.substr(UploadFS.config.storesPath.length + 1); - - if (!path) { - next(); - return; - } - - const allowCORS = () => { - // res.setHeader('Access-Control-Allow-Origin', req.headers.origin); - res.setHeader('Access-Control-Allow-Methods', 'POST'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); - }; - - if (req.method === 'OPTIONS') { - const regExp = new RegExp('^/([^/?]+)/([^/?]+)$'); - const match = regExp.exec(path); - - // Request is not valid - if (match === null) { - res.writeHead(400); - res.end(); - return; - } - - // Get store - const store = UploadFS.getStore(match[1]); - if (!store) { - res.writeHead(404); - res.end(); - return; - } - - // If a store is found, go ahead and allow the origin - allowCORS(); - - next(); - } else if (req.method === 'POST') { - res.writeHead(404); - res.end(); - } else if (req.method === 'GET') { - // Get store, file Id and file name - const regExp = new RegExp('^/([^/?]+)/([^/?]+)(?:/([^/?]+))?$'); - const match = regExp.exec(path); - - // Avoid 504 Gateway timeout error - // if file is not handled by UploadFS. - if (match === null) { - next(); - return; - } - - // Get store - const storeName = match[1]; - const store = UploadFS.getStore(storeName); - - if (!store) { - res.writeHead(404); - res.end(); - return; - } - - if (store.onRead !== null && store.onRead !== undefined && typeof store.onRead !== 'function') { - console.error(`ufs: Store.onRead is not a function in store "${storeName}"`); - res.writeHead(500); - res.end(); - return; - } - - // Remove file extension from file Id - const index = match[2].indexOf('.'); - const fileId = index !== -1 ? match[2].substr(0, index) : match[2]; - - // Get file from database - const file = await store.getCollection().findOne({ _id: fileId }); - if (!file) { - res.writeHead(404); - res.end(); - return; - } - - try { - // Check if the file can be accessed - if ((await store.onRead.call(store, fileId, file, req, res)) !== false) { - const options: { - start?: number; - end?: number; - } = {}; - let status = 200; - - // Prepare response headers - const headers: Record = { - 'Content-Type': file.type, - 'Content-Length': file.size, - }; - - // Add ETag header - if (typeof file.etag === 'string') { - headers.ETag = file.etag; - } - - // Add Last-Modified header - if (file.modifiedAt instanceof Date) { - headers['Last-Modified'] = file.modifiedAt.toUTCString(); - } else if (file.uploadedAt instanceof Date) { - headers['Last-Modified'] = file.uploadedAt.toUTCString(); - } - - // Parse request headers - if (typeof req.headers === 'object') { - // Compare ETag - if (req.headers['if-none-match']) { - if (file.etag === req.headers['if-none-match']) { - res.writeHead(304); // Not Modified - res.end(); - return; - } - } - - // Compare file modification date - if (req.headers['if-modified-since']) { - const modifiedSince = new Date(req.headers['if-modified-since']); - - if ( - (file.modifiedAt instanceof Date && file.modifiedAt > modifiedSince) || - // eslint-disable-next-line no-mixed-operators - (file.uploadedAt instanceof Date && file.uploadedAt > modifiedSince) - ) { - res.writeHead(304); // Not Modified - res.end(); - return; - } - } - - // Support range request - if (typeof req.headers.range === 'string') { - const { range } = req.headers; - - // Range is not valid - if (!range) { - res.writeHead(416); - res.end(); - return; - } - - const total = file.size || 0; - const unit = range.substr(0, range.indexOf('=')); - - if (unit !== 'bytes') { - res.writeHead(416); - res.end(); - return; - } - - const ranges = range - .substr(unit.length) - .replace(/[^0-9\-,]/, '') - .split(','); - - if (ranges.length > 1) { - // todo: support multipart ranges: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests - } else { - const r = ranges[0].split('-'); - const start = parseInt(r[0], 10); - const end = r[1] ? parseInt(r[1], 10) : total - 1; - - // Range is not valid - if (start < 0 || end >= total || start > end) { - res.writeHead(416); - res.end(); - return; - } - - // Update headers - headers['Content-Range'] = `bytes ${start}-${end}/${total}`; - headers['Content-Length'] = end - start + 1; - options.start = start; - options.end = end; - } - status = 206; // partial content - } - } else { - headers['Accept-Ranges'] = 'bytes'; - } - - // Open the file stream - const rs = await store.getReadStream(fileId, file, options); - const ws = new stream.PassThrough(); - - rs.on('error', (err) => { - store.onReadError.call(store, err, fileId, file); - res.end(); - }); - ws.on('error', (err) => { - store.onReadError.call(store, err, fileId, file); - res.end(); - }); - ws.on('close', () => { - // Close output stream at the end - ws.emit('end'); - }); - - // Transform stream - store.transformRead(rs, ws, fileId, file, req, headers); - - // Parse request headers - if (typeof req.headers === 'object') { - // Compress data using if needed (ignore audio/video as they are already compressed) - if (typeof req.headers['accept-encoding'] === 'string' && (!file.type || !/^(audio|video)/.test(file.type))) { - const accept = req.headers['accept-encoding']; - - // Compress with gzip - if (accept.match(/\bgzip\b/)) { - headers['Content-Encoding'] = 'gzip'; - delete headers['Content-Length']; - res.writeHead(status, headers); - await pipeline(ws, zlib.createGzip(), res); - return; - } - // Compress with deflate - if (accept.match(/\bdeflate\b/)) { - headers['Content-Encoding'] = 'deflate'; - delete headers['Content-Length']; - res.writeHead(status, headers); - await pipeline(ws, zlib.createDeflate(), res); - return; - } - } - } - - // Send raw data - if (!headers['Content-Encoding']) { - res.writeHead(status, headers); - await pipeline(ws, res); - } - } else { - res.end(); - } - } catch (err) { - console.error(`ufs: ${err instanceof Error ? err.message : String(err)}`); - if (!res.headersSent) { - res.writeHead(500); - } - if (!res.writableEnded) { - res.end(); - } - } - } else { - next(); - } -}); diff --git a/apps/meteor/server/ufs/ufs-store.ts b/apps/meteor/server/ufs/ufs-store.ts index 54ea2d8317bf0..71ad6508b5554 100644 --- a/apps/meteor/server/ufs/ufs-store.ts +++ b/apps/meteor/server/ufs/ufs-store.ts @@ -170,12 +170,10 @@ export class Store { // Set file attribute file.complete = true; file.etag = UploadFS.generateEtag(); - file.path = await this.getFileRelativeURL(fileId); file.progress = 1; file.token = this.generateToken(); file.uploading = false; file.uploadedAt = new Date(); - file.url = await this.getFileURL(fileId); // Execute callback if (typeof this.onFinishUpload === 'function') { @@ -190,13 +188,11 @@ export class Store { $set: { complete: file.complete, etag: file.etag, - path: file.path, progress: file.progress, size: file.size, token: file.token, uploading: file.uploading, uploadedAt: file.uploadedAt, - url: file.url, }, }, { session: options?.session }, @@ -257,16 +253,6 @@ export class Store { throw new Error('Store.getFilePath is not implemented'); } - async getFileRelativeURL(fileId: string) { - const file = await this.getCollection().findOne(fileId, { projection: { name: 1 } }); - return file ? this.getRelativeURL(`${fileId}/${file.name}`) : undefined; - } - - async getFileURL(fileId: string) { - const file = await this.getCollection().findOne(fileId, { projection: { name: 1 } }); - return file ? this.getURL(`${fileId}/${file.name}`) : undefined; - } - getFilter() { return this.options.filter; } @@ -279,21 +265,6 @@ export class Store { throw new Error('Store.getReadStream is not implemented'); } - getRelativeURL(path: string) { - const rootUrl = Meteor.absoluteUrl().replace(/\/+$/, ''); - const rootPath = rootUrl.replace(/^[a-z]+:\/\/[^/]+\/*/gi, ''); - const storeName = this.getName(); - path = String(path).replace(/\/$/, '').trim(); - return encodeURI(`${rootPath}/${UploadFS.config.storesPath}/${storeName}/${path}`); - } - - getURL(path: string) { - const rootUrl = Meteor.absoluteUrl('', { secure: UploadFS.config.https }).replace(/\/+$/, ''); - const storeName = this.getName(); - path = String(path).replace(/\/$/, '').trim(); - return encodeURI(`${rootUrl}/${UploadFS.config.storesPath}/${storeName}/${path}`); - } - async getRedirectURL(_file: IUpload, _forceDownload = false): Promise { throw new Error('getRedirectURL is not implemented'); } diff --git a/apps/meteor/tests/end-to-end/api/rooms.ts b/apps/meteor/tests/end-to-end/api/rooms.ts index 350c31c190454..262c60d4f70a9 100644 --- a/apps/meteor/tests/end-to-end/api/rooms.ts +++ b/apps/meteor/tests/end-to-end/api/rooms.ts @@ -335,7 +335,6 @@ describe('[Rooms]', () => { }); let fileNewUrl: string; - let fileOldUrl: string; let fileId: string; it('should upload a PNG file to room', async () => { await request @@ -353,7 +352,6 @@ describe('[Rooms]', () => { // expect(res.body.message.files[0]).to.have.property('name', '1024x1024.png'); fileNewUrl = res.body.file.url; - fileOldUrl = res.body.file.url.replace('/file-upload/', '/ufs/GridFS:Uploads/'); fileId = res.body.file._id; }); @@ -427,7 +425,6 @@ describe('[Rooms]', () => { expect(res.body.file).to.have.property('url'); fileNewUrl = res.body.file.url; - fileOldUrl = res.body.file.url.replace('/file-upload/', '/ufs/GridFS:Uploads/'); fileId = res.body.file._id; }); @@ -467,7 +464,6 @@ describe('[Rooms]', () => { expect(res.body.file).to.have.property('url'); fileNewUrl = res.body.file.url; - fileOldUrl = res.body.file.url.replace('/file-upload/', '/ufs/GridFS:Uploads/'); fileId = res.body.file._id; }); @@ -501,36 +497,30 @@ describe('[Rooms]', () => { it('should be able to get the file', async () => { await request.get(fileNewUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); }); it('should be able to get the file when no access to the room if setting allows it', async () => { await updateSetting('FileUpload_Restrict_to_room_members', false); await request.get(fileNewUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).set(userCredentials).expect('Content-Type', 'image/png').expect(200); }); it('should not be able to get the file when no access to the room if setting blocks', async () => { await updateSetting('FileUpload_Restrict_to_room_members', true); await request.get(fileNewUrl).set(userCredentials).expect(403); - await request.get(fileOldUrl).set(userCredentials).expect(403); }); it('should be able to get the file if member and setting blocks outside access', async () => { await updateSetting('FileUpload_Restrict_to_room_members', true); await request.get(fileNewUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).set(credentials).expect('Content-Type', 'image/png').expect(200); }); it('should not be able to get the file without credentials', async () => { await request.get(fileNewUrl).attach('file', imgURL).expect(403); - await request.get(fileOldUrl).attach('file', imgURL).expect(403); }); it('should be able to get the file without credentials if setting allows', async () => { await updateSetting('FileUpload_ProtectFiles', false); await request.get(fileNewUrl).expect('Content-Type', 'image/png').expect(200); - await request.get(fileOldUrl).expect('Content-Type', 'image/png').expect(200); }); it('should generate thumbnail for SVG files correctly', async () => {