[web] reject upload requests without a file path (#27156)

* [web] reject upload requests without a file path

* [web] update copy on error message and link to contact form

Co-authored-by: Kamal Arkinstall <kamal.arkinstall@overleaf.com>

* [web] update copy: move dot to the end

---------

Co-authored-by: Kamal Arkinstall <kamal.arkinstall@overleaf.com>
GitOrigin-RevId: ba1ee81a91b046540caeb2f3f3da0e305611b35f
This commit is contained in:
Jakob Ackermann
2025-07-21 16:02:43 +02:00
committed by Copybot
parent 81f0807fc6
commit 082121d3da
5 changed files with 71 additions and 4 deletions

View File

@@ -66,7 +66,7 @@ function uploadProject(req, res, next) {
async function uploadFile(req, res, next) {
const timer = new metrics.Timer('file-upload')
const name = req.body.name
const path = req.file?.path
const { path } = req.file
const projectId = req.params.Project_id
const userId = SessionManager.getLoggedInUserId(req.session)
let { folder_id: folderId } = req.query
@@ -162,8 +162,14 @@ function multerMiddleware(req, res, next) {
.status(422)
.json({ success: false, error: req.i18n.translate('file_too_large') })
}
return next(err)
if (err) return next(err)
if (!req.file?.path) {
logger.info({ req }, 'missing req.file.path on upload')
return res
.status(400)
.json({ success: false, error: 'invalid_upload_request' })
}
next()
})
}

View File

@@ -867,6 +867,7 @@
"invalid_password_too_similar": "",
"invalid_regular_expression": "",
"invalid_request": "",
"invalid_upload_request": "",
"invite": "",
"invite_expired": "",
"invite_more_collabs": "",

View File

@@ -1,4 +1,4 @@
import { useTranslation } from 'react-i18next'
import { useTranslation, Trans } from 'react-i18next'
import { FetchError } from '../../../../infrastructure/fetch-json'
import RedirectToLogin from './redirect-to-login'
import {
@@ -7,6 +7,7 @@ import {
InvalidFilenameError,
} from '../../errors'
import DangerMessage from './danger-message'
import getMeta from '@/utils/meta'
// TODO: Update the error type when we properly type FileTreeActionableContext
export default function ErrorMessage({
@@ -15,6 +16,7 @@ export default function ErrorMessage({
error: string | Record<string, any>
}) {
const { t } = useTranslation()
const { isOverleaf } = getMeta('ol-ExposedSettings')
const fileNameLimit = 150
// the error is a string
@@ -46,6 +48,22 @@ export default function ErrorMessage({
</DangerMessage>
)
case 'invalid_upload_request':
if (!isOverleaf) {
return (
<DangerMessage>{t('generic_something_went_wrong')}</DangerMessage>
)
}
return (
<DangerMessage>
<Trans
i18nKey="invalid_upload_request"
// eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
components={[<a href="/contact" target="_blank" />]}
/>
</DangerMessage>
)
case 'duplicate_file_name':
return (
<DangerMessage>

View File

@@ -1111,6 +1111,7 @@
"invalid_password_too_similar": "Password is too similar to parts of email address",
"invalid_regular_expression": "Invalid regular expression",
"invalid_request": "Invalid Request. Please correct the data and try again.",
"invalid_upload_request": "The upload failed. If the problem persists, <0>let us know</0>.",
"invalid_zip_file": "Invalid zip file",
"invite": "Invite",
"invite_expired": "The invite may have expired",

View File

@@ -138,6 +138,47 @@ describe('ProjectStructureChanges', function () {
})
})
describe('when sending an upload request without a file', function () {
describe('project', function () {
it('should reject the request with status 400', async function () {
const { response, body } = await owner.doRequest('POST', {
uri: 'project/new/upload',
json: true,
formData: {
name: 'foo',
},
})
expect(response.statusCode).to.equal(400)
expect(body).to.deep.equal({
success: false,
error: 'invalid_upload_request',
})
})
})
describe('file', function () {
it('should reject the request with status 400', async function () {
const projectId = await owner.createProject('foo', {
template: 'blank',
})
const { response, body } = await owner.doRequest('POST', {
uri: `project/${projectId}/upload`,
json: true,
formData: {
name: 'foo.txt',
},
})
expect(response.statusCode).to.equal(400)
expect(body).to.deep.equal({
success: false,
error: 'invalid_upload_request',
})
})
})
})
describe('uploading an empty zipfile', function () {
let res