فهرست منبع

refactor download route

Yuki Takei 2 سال پیش
والد
کامیت
ff8ba21bad

+ 5 - 89
apps/app/src/server/routes/attachment/api.js

@@ -162,103 +162,19 @@ export const routesFactory = (crowi) => {
     return await Page.isAccessiblePageByViewer(attachment.page, user);
   }
 
-  /**
-   * Common method to response
-   *
-   * @param {Request} req
-   * @param {Response} res
-   * @param {User} user
-   * @param {Attachment} attachment
-   * @param {boolean} forceDownload
-   */
-  async function responseForAttachment(req, res, attachment, forceDownload) {
-    const { fileUploadService } = crowi;
-
-    // add headers before evaluating 'req.fresh'
-    setHeaderToRes(res, attachment, forceDownload);
-
-    // return 304 if request is "fresh"
-    // see: http://expressjs.com/en/5x/api.html#req.fresh
-    if (req.fresh) {
-      return res.sendStatus(304);
-    }
-
-    if (fileUploadService.canRespond()) {
-      return fileUploadService.respond(res, attachment);
-    }
-
-    let fileStream;
-    try {
-      fileStream = await fileUploadService.findDeliveryFile(attachment);
-    }
-    catch (e) {
-      logger.error(e);
-      return res.json(ApiResponse.error(e.message));
-    }
-
-    const parameters = {
-      ip:  req.ip,
-      endpoint: req.originalUrl,
-      action: SupportedAction.ACTION_ATTACHMENT_DOWNLOAD,
-      user: req.user?._id,
-      snapshot: {
-        username: req.user?.username,
-      },
-    };
-    await crowi.activityService.createActivity(parameters);
-
-    return fileStream.pipe(res);
-  }
-
-  /**
-   * set http response header
-   *
-   * @param {Response} res
-   * @param {Attachment} attachment
-   * @param {boolean} forceDownload
-   */
-  function setHeaderToRes(res, attachment, forceDownload) {
-    res.set({
-      ETag: `Attachment-${attachment._id}`,
-      'Last-Modified': attachment.createdAt.toUTCString(),
-    });
-
-    if (attachment.fileSize) {
-      res.set({
-        'Content-Length': attachment.fileSize,
-      });
-    }
-
-    // download
-    if (forceDownload) {
-      res.set({
-        'Content-Disposition': `attachment;filename*=UTF-8''${encodeURIComponent(attachment.originalName)}`,
-      });
-    }
-    // reference
-    else {
-      res.set({
-        'Content-Type': attachment.fileFormat,
-        // eslint-disable-next-line max-len
-        'Content-Security-Policy': "script-src 'unsafe-hashes'; style-src 'self' 'unsafe-inline'; object-src 'none'; require-trusted-types-for 'script'; media-src 'self'; default-src 'none';",
-        'Content-Disposition': `inline;filename*=UTF-8''${encodeURIComponent(attachment.originalName)}`,
-      });
-    }
-  }
-
 
   const actions = {};
   const api = {};
 
   actions.api = api;
 
-  api.download = async function(req, res) {
-    const id = req.params.id;
+  // api.download = async function(req, res) {
+  //   const id = req.params.id;
 
-    const attachment = await Attachment.findById(id);
+  //   const attachment = await Attachment.findById(id);
 
-    return responseForAttachment(req, res, attachment, true);
-  };
+  //   return responseForAttachment(req, res, attachment, true);
+  // };
 
   /**
    * @swagger

+ 59 - 0
apps/app/src/server/routes/attachment/download.ts

@@ -0,0 +1,59 @@
+import express from 'express';
+import type { Router } from 'express';
+
+import { SupportedAction } from '~/interfaces/activity';
+import type { CrowiRequest } from '~/interfaces/crowi-request';
+import loggerFactory from '~/utils/logger';
+
+import type Crowi from '../../crowi';
+import { certifySharedPageAttachmentMiddleware } from '../../middlewares/certify-shared-page-attachment';
+
+import {
+  GetRequest, GetResponse, getActionFactory, validateGetRequest,
+} from './get';
+
+
+const logger = loggerFactory('growi:routes:attachment:download');
+
+
+const generateActivityParameters = (req: CrowiRequest) => {
+  return {
+    ip:  req.ip,
+    endpoint: req.originalUrl,
+    action: SupportedAction.ACTION_ATTACHMENT_DOWNLOAD,
+    user: req.user?._id,
+    snapshot: {
+      username: req.user?.username,
+    },
+  };
+};
+
+export const downloadRouterFactory = (crowi: Crowi): Router => {
+
+  const loginRequired = require('../../middlewares/login-required')(crowi, true);
+
+  const router = express.Router();
+
+  // note: validateGetRequest requires `req.params.id`
+  router.get<{ id: string }>('/:id([0-9a-z]{24})',
+    certifySharedPageAttachmentMiddleware, loginRequired, validateGetRequest,
+    async(req: GetRequest, res: GetResponse) => {
+      const { attachment } = res.locals;
+
+      const activityParameters = generateActivityParameters(req);
+      const createActivity = async() => {
+        await crowi.activityService.createActivity(activityParameters);
+      };
+
+      res.set({
+        'Content-Disposition': `attachment;filename*=UTF-8''${encodeURIComponent(attachment.originalName)}`,
+      });
+
+      const getAction = getActionFactory(crowi, attachment);
+      await getAction(req, res);
+
+      createActivity();
+    });
+
+  return router;
+};

+ 7 - 40
apps/app/src/server/routes/attachment/get.ts

@@ -7,7 +7,6 @@ import type {
 } from 'express';
 import mongoose from 'mongoose';
 
-import { SupportedAction } from '~/interfaces/activity';
 import type { CrowiProperties, CrowiRequest } from '~/interfaces/crowi-request';
 import loggerFactory from '~/utils/logger';
 
@@ -75,37 +74,9 @@ export const setCommonHeadersToRes = (res: Response, attachment: IAttachmentDocu
       'Content-Length': attachment.fileSize,
     });
   }
-
-  // // download
-  // if (forceDownload) {
-  //   res.set({
-  //     'Content-Disposition': `attachment;filename*=UTF-8''${encodeURIComponent(attachment.originalName)}`,
-  //   });
-  // }
-  // // reference
-  // else {
-  //   res.set({
-  //     'Content-Type': attachment.fileFormat,
-  //     // eslint-disable-next-line max-len
-  //     'Content-Security-Policy': "script-src 'unsafe-hashes'; style-src 'self' 'unsafe-inline'; object-src 'none'; require-trusted-types-for 'script'; media-src 'self'; default-src 'none';",
-  //     'Content-Disposition': `inline;filename*=UTF-8''${encodeURIComponent(attachment.originalName)}`,
-  //   });
-  // }
 };
 
 
-const generateActivityParameters = (req: CrowiRequest) => {
-  return {
-    ip:  req.ip,
-    endpoint: req.originalUrl,
-    action: SupportedAction.ACTION_ATTACHMENT_DOWNLOAD,
-    user: req.user?._id,
-    snapshot: {
-      username: req.user?.username,
-    },
-  };
-};
-
 export const getActionFactory = (crowi: Crowi, attachment: IAttachmentDocument) => {
   return async(req: CrowiRequest, res: Response): Promise<void> => {
 
@@ -118,25 +89,17 @@ export const getActionFactory = (crowi: Crowi, attachment: IAttachmentDocument)
       'Content-Type': attachment.fileFormat,
       // eslint-disable-next-line max-len
       'Content-Security-Policy': "script-src 'unsafe-hashes'; style-src 'self' 'unsafe-inline'; object-src 'none'; require-trusted-types-for 'script'; media-src 'self'; default-src 'none';",
-      'Content-Disposition': `inline;filename*=UTF-8''${encodeURIComponent(attachment.originalName)}`,
     });
 
-    const activityParameters = generateActivityParameters(req);
-    const createActivity = async() => {
-      await crowi.activityService.createActivity(activityParameters);
-    };
-
     // return 304 if request is "fresh"
     // see: http://expressjs.com/en/5x/api.html#req.fresh
     if (req.fresh) {
       res.sendStatus(304);
-      createActivity();
       return;
     }
 
     if (fileUploadService.canRespond()) {
       fileUploadService.respond(res, attachment);
-      createActivity();
       return;
     }
 
@@ -150,19 +113,18 @@ export const getActionFactory = (crowi: Crowi, attachment: IAttachmentDocument)
       return;
     }
 
-    createActivity();
     return;
   };
 };
 
 
-type GetRequest = CrowiProperties & Request<
+export type GetRequest = CrowiProperties & Request<
   { id: string },
   any, any, any,
   LocalsAfterDataInjection
 >;
 
-type GetResponse = Response<
+export type GetResponse = Response<
   any,
   LocalsAfterDataInjection
 >
@@ -178,6 +140,11 @@ export const getRouterFactory = (crowi: Crowi): Router => {
     certifySharedPageAttachmentMiddleware, loginRequired, validateGetRequest,
     (req: GetRequest, res: GetResponse) => {
       const { attachment } = res.locals;
+
+      res.set({
+        'Content-Disposition': `inline;filename*=UTF-8''${encodeURIComponent(attachment.originalName)}`,
+      });
+
       const getAction = getActionFactory(crowi, attachment);
       getAction(req, res);
     });

+ 1 - 0
apps/app/src/server/routes/attachment/index.ts

@@ -1,2 +1,3 @@
 export * from './get';
 export * from './get-brand-logo';
+export * from './download';

+ 1 - 3
apps/app/src/server/routes/index.js

@@ -5,7 +5,6 @@ import { middlewareFactory as rateLimiterFactory } from '~/features/rate-limiter
 
 import { generateAddActivityMiddleware } from '../middlewares/add-activity';
 import apiV1FormValidator from '../middlewares/apiv1-form-validator';
-import { certifySharedPageAttachmentMiddleware } from '../middlewares/certify-shared-page-attachment';
 import { excludeReadOnlyUser } from '../middlewares/exclude-read-only-user';
 import injectResetOrderByTokenMiddleware from '../middlewares/inject-reset-order-by-token-middleware';
 import injectUserRegistrationOrderByTokenMiddleware from '../middlewares/inject-user-registration-order-by-token-middleware';
@@ -159,8 +158,7 @@ module.exports = function(crowi, app) {
   app.get('/me/*'                                 , loginRequiredStrictly, next.delegateToNext);
 
   app.use('/attachment', attachment.getRouterFactory(crowi));
-
-  app.get('/download/:id([0-9a-z]{24})'         , certifySharedPageAttachmentMiddleware, loginRequired, attachment.validateGetRequest, attachmentApi.download);
+  app.use('/download', attachment.downloadRouterFactory(crowi));
 
   app.get('/_search'                            , loginRequired, next.delegateToNext);