diff --git a/internal/servants/web/utils.go b/internal/servants/web/utils.go index e42e03b5..80bdede9 100644 --- a/internal/servants/web/utils.go +++ b/internal/servants/web/utils.go @@ -163,7 +163,7 @@ func getFileExt(s string) (string, mir.Error) { return ".mp4", nil case "video/quicktime": return ".mov", nil - case "application/zip": + case "application/zip", "application/x-zip", "application/octet-stream", "application/x-zip-compressed": return ".zip", nil default: return "", _errFileInvalidExt.WithDetails("仅允许 png/jpg/gif/mp4/mov/zip 类型") diff --git a/web/src/components/compose.vue b/web/src/components/compose.vue index b4faf3ec..79825463 100644 --- a/web/src/components/compose.vue +++ b/web/src/components/compose.vue @@ -278,6 +278,7 @@ import { } from '@vicons/ionicons5'; import { createPost } from '@/api/post'; import { parsePostTag } from '@/utils/content'; +import { isZipFile } from '@/utils/isZipFile'; import type { MentionOption, UploadFileInfo, UploadInst } from 'naive-ui'; import { VisibilityEnum, PostItemTypeEnum } from '@/utils/IEnum'; @@ -432,11 +433,9 @@ const beforeUpload = async (data: any) => { window.$message.warning('视频大小不能超过100MB'); return false; } - // 附件类型校验 if ( - uploadType.value === 'attachment' && - !['application/zip'].includes(data.file.file?.type) + uploadType.value === 'attachment' && !(await isZipFile(data.file.file)) ) { window.$message.warning('附件仅允许 zip 格式'); return false; diff --git a/web/src/utils/isZipFile.ts b/web/src/utils/isZipFile.ts new file mode 100644 index 00000000..ec939324 --- /dev/null +++ b/web/src/utils/isZipFile.ts @@ -0,0 +1,47 @@ + +export const isZipFile = (file: File): Promise => { + const fileReader = new FileReader(); + + const isValidZipFileType = (fileType: string): boolean => { + const zipFileTypes = ['application/zip', 'application/x-zip', 'application/octet-stream', 'application/x-zip-compressed']; + return zipFileTypes.includes(fileType); + }; + + const checkFileType = (): boolean => { + const arr = new Uint8Array(fileReader.result as ArrayBuffer).subarray(0, 4); + let header = ''; + for (let i = 0; i < arr.length; i++) { + header += arr[i].toString(16); + } + + switch (header) { + case '504b0304': + case '504b0506': + case '504b0708': + return isValidZipFileType('application/zip'); + case '504b030414': + return isValidZipFileType('application/x-zip-compressed'); + case '504b0508': + return isValidZipFileType('application/x-zip'); + case '504b5370': + return isValidZipFileType('application/octet-stream'); + default: + return false; + } + }; + + return new Promise((resolve, reject) => { + fileReader.onloadend = () => { + const fileType = file.type; + if (fileType === '' || fileType === 'application/octet-stream') { + // 如果浏览器不能识别文件类型,则进行手动检查 + resolve(checkFileType()); + } else { + // 如果浏览器可以识别文件类型,则根据文件类型进行检查 + resolve(isValidZipFileType(fileType)); + } + }; + + fileReader.readAsArrayBuffer(file.slice(0, 4)); + }); +}