Răsfoiți Sursa

[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
Jakob Ackermann 1 an în urmă
părinte
comite
082121d3da

+ 9 - 3
services/web/app/src/Features/Uploads/ProjectUploadController.mjs

@@ -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()
   })
 }
 

+ 1 - 0
services/web/frontend/extracted-translations.json

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

+ 19 - 1
services/web/frontend/js/features/file-tree/components/file-tree-create/error-message.tsx

@@ -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>

+ 1 - 0
services/web/locales/en.json

@@ -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",

+ 41 - 0
services/web/test/acceptance/src/ProjectStructureTests.mjs

@@ -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