From f6a3f6f5f10734c50e6c00b410d7ea12d1d8f505 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Tue, 8 Aug 2023 13:24:30 +0900
Subject: [PATCH] enhance(backend): Improve behavior of correctFilename
 (#11484)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* enhance(backend): Improve behavior of correctFilename

* :v:

* 未知のファイル形式かつ拡張子がある場合は何もしない

* :v:

* .ext

---------

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                                  |  1 +
 packages/backend/src/misc/correct-filename.ts | 54 +++++++++++++++----
 .../test/unit/misc/correct-filename.ts        | 48 +++++++++++++++++
 packages/backend/test/unit/misc/others.ts     | 28 ----------
 pnpm-lock.yaml                                | 17 ++----
 5 files changed, 99 insertions(+), 49 deletions(-)
 create mode 100644 packages/backend/test/unit/misc/correct-filename.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 763b577494..e2620db00d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@
 
 ### Server
 - cacheRemoteFilesの初期値はfalseになりました
+- ファイルアップロード時等にファイル名の拡張子を修正する関数(correctFilename)の挙動を改善
 - fix: muteがapiからのuser list timeline取得で機能しない問題を修正
 
 ## 13.14.2
diff --git a/packages/backend/src/misc/correct-filename.ts b/packages/backend/src/misc/correct-filename.ts
index a702f0be0d..34cb458a2c 100644
--- a/packages/backend/src/misc/correct-filename.ts
+++ b/packages/backend/src/misc/correct-filename.ts
@@ -3,18 +3,54 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-// 与えられた拡張子とファイル名が一致しているかどうかを確認し、
-// 一致していない場合は拡張子を付与して返す
+/**
+ * Array.includes()よりSet.has()の方が高速
+ */
+const targetExtsToSkip = new Set([
+	'.gz',
+	'.tar',
+	'.tgz',
+	'.bz2',
+	'.xz',
+	'.zip',
+	'.7z',
+]);
+
+const extRegExp = /\.[0-9a-zA-Z]+$/i;
+
+/**
+ * 与えられた拡張子とファイル名が一致しているかどうかを確認し、
+ * 一致していない場合は拡張子を付与して返す
+ * 
+ * extはfile-typeのextを想定
+ */
 export function correctFilename(filename: string, ext: string | null) {
-	const dotExt = ext ? ext.startsWith('.') ? ext : `.${ext}` : '.unknown';
-	if (filename.endsWith(dotExt)) {
-		return filename;
-	}
-	if (ext === 'jpg' && filename.endsWith('.jpeg')) {
-		return filename;
+	const dotExt = ext ? ext[0] === '.' ? ext : `.${ext}` : '.unknown';
+
+	const match = extRegExp.exec(filename);
+	if (!match || !match[0]) {
+		// filenameが拡張子を持っていない場合は拡張子をつける
+		return `${filename}${dotExt}`;
 	}
-	if (ext === 'tif' && filename.endsWith('.tiff')) {
+
+	const filenameExt = match[0].toLowerCase();
+	if (
+		// 未知のファイル形式かつ拡張子がある場合は何もしない
+		ext === null ||
+		// 拡張子が一致している場合は何もしない
+		filenameExt === dotExt ||
+
+		// jpeg, tiffを同一視
+		dotExt === '.jpg' && filenameExt === '.jpeg' ||
+		dotExt === '.tif' && filenameExt === '.tiff' ||
+
+		// 圧縮形式っぽければ下手に拡張子を変えない
+		// https://github.com/misskey-dev/misskey/issues/11482
+		targetExtsToSkip.has(dotExt)
+	) {
 		return filename;
 	}
+
+	// 拡張子があるが一致していないなどの場合は拡張子を付け足す
 	return `${filename}${dotExt}`;
 }
diff --git a/packages/backend/test/unit/misc/correct-filename.ts b/packages/backend/test/unit/misc/correct-filename.ts
new file mode 100644
index 0000000000..8138b2361f
--- /dev/null
+++ b/packages/backend/test/unit/misc/correct-filename.ts
@@ -0,0 +1,48 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { correctFilename } from '@/misc/correct-filename.js';
+
+describe(correctFilename, () => {
+    it('no ext to null', () => {
+        expect(correctFilename('test', null)).toBe('test.unknown');
+    });
+    it('no ext to jpg', () => {
+        expect(correctFilename('test', 'jpg')).toBe('test.jpg');
+    });
+    it('jpg to webp', () => {
+        expect(correctFilename('test.jpg', 'webp')).toBe('test.jpg.webp');
+    });
+    it('jpg to .webp', () => {
+        expect(correctFilename('test.jpg', '.webp')).toBe('test.jpg.webp');
+    });
+    it('jpeg to jpg', () => {
+        expect(correctFilename('test.jpeg', 'jpg')).toBe('test.jpeg');
+    });
+    it('JPEG to jpg', () => {
+        expect(correctFilename('test.JPEG', 'jpg')).toBe('test.JPEG');
+    });
+    it('jpg to jpg', () => {
+        expect(correctFilename('test.jpg', 'jpg')).toBe('test.jpg');
+    });
+    it('JPG to jpg', () => {
+        expect(correctFilename('test.JPG', 'jpg')).toBe('test.JPG');
+    });
+    it('tiff to tif', () => {
+        expect(correctFilename('test.tiff', 'tif')).toBe('test.tiff');
+    });
+    it('skip gz', () => {
+        expect(correctFilename('test.unitypackage', 'gz')).toBe('test.unitypackage');
+    });
+    it('skip text file', () => {
+        expect(correctFilename('test.txt', null)).toBe('test.txt');
+    });
+    it('unknown', () => {
+        expect(correctFilename('test.hoge', null)).toBe('test.hoge');
+    });
+	test('non ascii with space', () => {
+		expect(correctFilename('ファイル 名前', 'jpg')).toBe('ファイル 名前.jpg');
+	});
+});
diff --git a/packages/backend/test/unit/misc/others.ts b/packages/backend/test/unit/misc/others.ts
index e2e484dfd7..b16d26d866 100644
--- a/packages/backend/test/unit/misc/others.ts
+++ b/packages/backend/test/unit/misc/others.ts
@@ -5,7 +5,6 @@
 
 import { describe, test, expect } from '@jest/globals';
 import { contentDisposition } from '@/misc/content-disposition.js';
-import { correctFilename } from '@/misc/correct-filename.js';
 
 describe('misc:content-disposition', () => {
 	test('inline', () => {
@@ -18,30 +17,3 @@ describe('misc:content-disposition', () => {
 		expect(contentDisposition('attachment', 'ファイル名')).toBe('attachment; filename=\"_____\"; filename*=UTF-8\'\'%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D');
 	});
 });
-
-describe('misc:correct-filename', () => {
-	test('simple', () => {
-		expect(correctFilename('filename', 'jpg')).toBe('filename.jpg');
-	});
-	test('with same ext', () => {
-		expect(correctFilename('filename.jpg', 'jpg')).toBe('filename.jpg');
-	});
-	test('.ext', () => {
-		expect(correctFilename('filename.jpg', '.jpg')).toBe('filename.jpg');
-	});
-	test('with different ext', () => {
-		expect(correctFilename('filename.webp', 'jpg')).toBe('filename.webp.jpg');
-	});
-	test('non ascii with space', () => {
-		expect(correctFilename('ファイル 名前', 'jpg')).toBe('ファイル 名前.jpg');
-	});
-	test('jpeg', () => {
-		expect(correctFilename('filename.jpeg', 'jpg')).toBe('filename.jpeg');
-	});
-	test('tiff', () => {
-		expect(correctFilename('filename.tiff', 'tif')).toBe('filename.tiff');
-	});
-	test('null ext', () => {
-		expect(correctFilename('filename', null)).toBe('filename.unknown');
-	});
-});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 13fc490731..1a559e867e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -358,7 +358,7 @@ importers:
         version: 2.1.0
       summaly:
         specifier: github:misskey-dev/summaly
-        version: github.com/misskey-dev/summaly/089a0ad8e8c780e5c088b1c528aa95c5827cbdcc
+        version: github.com/misskey-dev/summaly/d2d8db49943ccb201c1b1b283e9d0a630519fac7
       systeminformation:
         specifier: 5.18.9
         version: 5.18.9
@@ -997,7 +997,7 @@ importers:
         version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.27)(@storybook/components@7.1.0)(@storybook/core-events@7.0.27)(@storybook/manager-api@7.0.27)(@storybook/preview-api@7.0.27)(@storybook/theming@7.0.27)(@storybook/types@7.0.27)(react-dom@18.2.0)(react@18.2.0)
       summaly:
         specifier: github:misskey-dev/summaly
-        version: github.com/misskey-dev/summaly/089a0ad8e8c780e5c088b1c528aa95c5827cbdcc
+        version: github.com/misskey-dev/summaly/d2d8db49943ccb201c1b1b283e9d0a630519fac7
       vite-plugin-turbosnap:
         specifier: 1.0.2
         version: 1.0.2
@@ -5345,7 +5345,7 @@ packages:
     hasBin: true
     requiresBuild: true
     dependencies:
-      detect-libc: 2.0.1
+      detect-libc: 2.0.2
       https-proxy-agent: 5.0.1
       make-dir: 3.1.0
       node-fetch: 2.6.11
@@ -11496,13 +11496,6 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /detect-libc@2.0.1:
-    resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
-    engines: {node: '>=8'}
-    requiresBuild: true
-    dev: false
-    optional: true
-
   /detect-libc@2.0.2:
     resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
     engines: {node: '>=8'}
@@ -22129,8 +22122,8 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  github.com/misskey-dev/summaly/089a0ad8e8c780e5c088b1c528aa95c5827cbdcc:
-    resolution: {tarball: https://codeload.github.com/misskey-dev/summaly/tar.gz/089a0ad8e8c780e5c088b1c528aa95c5827cbdcc}
+  github.com/misskey-dev/summaly/d2d8db49943ccb201c1b1b283e9d0a630519fac7:
+    resolution: {tarball: https://codeload.github.com/misskey-dev/summaly/tar.gz/d2d8db49943ccb201c1b1b283e9d0a630519fac7}
     name: summaly
     version: 4.0.2
     dependencies:
-- 
GitLab