mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
fix(plugin-file-manager): fix file issues (#6436)
* fix(plugin-file-manager): fix file issues * fix(plugin-file-manager): fix special char in windows
This commit is contained in:
parent
c408c916d7
commit
cc0e13dce0
@ -17,7 +17,7 @@ server {
|
||||
server_name _;
|
||||
root /app/nocobase/packages/app/client/dist;
|
||||
index index.html;
|
||||
client_max_body_size 20M;
|
||||
client_max_body_size 0;
|
||||
|
||||
access_log /var/log/nginx/nocobase.log apm;
|
||||
|
||||
|
@ -17,7 +17,7 @@ server {
|
||||
server_name _;
|
||||
root /app/nocobase/node_modules/@nocobase/app/dist/client;
|
||||
index index.html;
|
||||
client_max_body_size 1000M;
|
||||
client_max_body_size 0;
|
||||
access_log /var/log/nginx/nocobase.log apm;
|
||||
|
||||
gzip on;
|
||||
|
@ -17,7 +17,7 @@ server {
|
||||
server_name _;
|
||||
root {{cwd}}/node_modules/@nocobase/app/dist/client;
|
||||
index index.html;
|
||||
client_max_body_size 1000M;
|
||||
client_max_body_size 0;
|
||||
access_log /var/log/nginx/nocobase.log apm;
|
||||
|
||||
gzip on;
|
||||
|
@ -199,11 +199,29 @@ export interface URLReadPrettyProps {
|
||||
}
|
||||
|
||||
const ellipsisStyle = { textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap', display: 'block' };
|
||||
|
||||
function encodeFileURL(url: string): string {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const parts = url.split('/');
|
||||
const filename = parts.pop();
|
||||
parts.push(encodeURIComponent(filename));
|
||||
const encodedURL = parts.join('/');
|
||||
return encodedURL;
|
||||
}
|
||||
|
||||
ReadPretty.URL = (props: URLReadPrettyProps) => {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const prefixCls = usePrefixCls('description-url', props);
|
||||
const content = props.value && (
|
||||
<a style={props.ellipsis ? ellipsisStyle : undefined} target="_blank" rel="noopener noreferrer" href={props.value}>
|
||||
<a
|
||||
style={props.ellipsis ? ellipsisStyle : undefined}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={encodeFileURL(props.value)}
|
||||
>
|
||||
{props.value}
|
||||
</a>
|
||||
);
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
toValueItem as toValueItemDefault,
|
||||
useBeforeUpload,
|
||||
useUploadProps,
|
||||
encodeFileURL,
|
||||
} from './shared';
|
||||
import { useStyles } from './style';
|
||||
import type { ComposedUpload, DraggerProps, DraggerV2Props, UploadProps } from './type';
|
||||
@ -89,26 +90,27 @@ attachmentFileTypes.add({
|
||||
},
|
||||
});
|
||||
|
||||
const iframePreviewSupportedTypes = ['application/pdf', 'audio/*', 'image/*', 'video/*'];
|
||||
const iframePreviewSupportedTypes = ['application/pdf', 'audio/*', 'image/*', 'video/*', 'text/*'];
|
||||
|
||||
function IframePreviewer({ index, list, onSwitchIndex }) {
|
||||
const { t } = useTranslation();
|
||||
const file = list[index];
|
||||
const url = encodeFileURL(file.url);
|
||||
const onOpen = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(file.url);
|
||||
window.open(url);
|
||||
},
|
||||
[file],
|
||||
[url],
|
||||
);
|
||||
const onDownload = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
saveAs(file.url, `${file.title}${file.extname}`);
|
||||
saveAs(url, `${file.title}${file.extname}`);
|
||||
},
|
||||
[file],
|
||||
[file.extname, file.title, url],
|
||||
);
|
||||
const onClose = useCallback(() => {
|
||||
onSwitchIndex(null);
|
||||
@ -148,7 +150,7 @@ function IframePreviewer({ index, list, onSwitchIndex }) {
|
||||
>
|
||||
{iframePreviewSupportedTypes.some((type) => matchMimetype(file, type)) ? (
|
||||
<iframe
|
||||
src={file.url}
|
||||
src={url}
|
||||
style={{
|
||||
width: '100%',
|
||||
maxHeight: '90vh',
|
||||
@ -390,7 +392,7 @@ export function Uploader({ rules, ...props }: UploadProps) {
|
||||
} else {
|
||||
field.setFeedback({});
|
||||
}
|
||||
}, [field, pendingList]);
|
||||
}, [field, pendingList, t]);
|
||||
|
||||
const onUploadChange = useCallback(
|
||||
(info) => {
|
||||
|
@ -267,3 +267,15 @@ export function useBeforeUpload(rules) {
|
||||
[rules],
|
||||
);
|
||||
}
|
||||
|
||||
export function encodeFileURL(url: string): string {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
const parts = url.split('/');
|
||||
const filename = parts.pop();
|
||||
parts.push(encodeURIComponent(filename));
|
||||
const encodedURL = parts.join('/');
|
||||
return encodedURL;
|
||||
}
|
||||
|
@ -20,7 +20,6 @@
|
||||
"@types/multer": "^1.4.5",
|
||||
"antd": "5.x",
|
||||
"cos-nodejs-sdk-v5": "^2.11.14",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"koa-static": "^5.0.0",
|
||||
"mime-match": "^1.0.2",
|
||||
"mkdirp": "~0.5.4",
|
||||
|
@ -10,7 +10,7 @@
|
||||
export const FILE_FIELD_NAME = 'file';
|
||||
export const LIMIT_FILES = 1;
|
||||
export const FILE_SIZE_LIMIT_MIN = 1;
|
||||
export const FILE_SIZE_LIMIT_MAX = 1024 * 1024 * 1024;
|
||||
export const FILE_SIZE_LIMIT_MAX = Number.POSITIVE_INFINITY;
|
||||
export const FILE_SIZE_LIMIT_DEFAULT = 1024 * 1024 * 20;
|
||||
|
||||
export const STORAGE_TYPE_LOCAL = 'local';
|
||||
|
@ -9,9 +9,9 @@
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import querystring from 'querystring';
|
||||
import { getApp } from '.';
|
||||
import { FILE_FIELD_NAME, FILE_SIZE_LIMIT_DEFAULT, STORAGE_TYPE_LOCAL } from '../../constants';
|
||||
import PluginFileManagerServer from '../server';
|
||||
|
||||
const { LOCAL_STORAGE_BASE_URL, LOCAL_STORAGE_DEST = 'storage/uploads', APP_PORT = '13000' } = process.env;
|
||||
|
||||
@ -105,6 +105,35 @@ describe('action', () => {
|
||||
const content = await agent.get(url);
|
||||
expect(content.text.includes('Hello world!')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('filename with special character (URL)', async () => {
|
||||
const rawText = '[]中文报告! 1%~50.4% (123) {$#}';
|
||||
const rawFilename = `${rawText}.txt`;
|
||||
const { body } = await agent.resource('attachments').create({
|
||||
[FILE_FIELD_NAME]: path.resolve(__dirname, `./files/${rawFilename}`),
|
||||
});
|
||||
|
||||
const matcher = {
|
||||
title: rawText,
|
||||
extname: '.txt',
|
||||
path: '',
|
||||
mimetype: 'text/plain',
|
||||
meta: {},
|
||||
storageId: 1,
|
||||
};
|
||||
|
||||
// 文件上传和解析是否正常
|
||||
expect(body.data).toMatchObject(matcher);
|
||||
// 文件的 url 是否正常生成
|
||||
expect(body.data.url).toBe(`${DEFAULT_LOCAL_BASE_URL}${body.data.path}/${body.data.filename}`);
|
||||
|
||||
const encodedFilename = querystring.escape(rawFilename);
|
||||
// console.log('-----------', body.data, encodedFilename);
|
||||
// 文件的 url 是否正常访问
|
||||
// TODO: mock-server is not start within gateway, static url can not be accessed
|
||||
// const res2 = await agent.get(`${DEFAULT_LOCAL_BASE_URL}${body.data.path}/${encodedFilename}`);
|
||||
// expect(res2.text).toBe(rawText);
|
||||
});
|
||||
});
|
||||
|
||||
describe('specific storage', () => {
|
||||
|
@ -0,0 +1 @@
|
||||
[]中文报告! 1%~50.4% (123) {$#}
|
@ -12,13 +12,7 @@ import { koaMulter as multer } from '@nocobase/utils';
|
||||
import Path from 'path';
|
||||
|
||||
import Plugin from '..';
|
||||
import {
|
||||
FILE_FIELD_NAME,
|
||||
FILE_SIZE_LIMIT_DEFAULT,
|
||||
FILE_SIZE_LIMIT_MAX,
|
||||
FILE_SIZE_LIMIT_MIN,
|
||||
LIMIT_FILES,
|
||||
} from '../../constants';
|
||||
import { FILE_FIELD_NAME, FILE_SIZE_LIMIT_DEFAULT, FILE_SIZE_LIMIT_MIN, LIMIT_FILES } from '../../constants';
|
||||
import * as Rules from '../rules';
|
||||
import { StorageClassType } from '../storages';
|
||||
|
||||
@ -90,10 +84,7 @@ async function multipart(ctx: Context, next: Next) {
|
||||
},
|
||||
storage: storageInstance.make(),
|
||||
};
|
||||
multerOptions.limits['fileSize'] = Math.min(
|
||||
Math.max(FILE_SIZE_LIMIT_MIN, storage.rules.size ?? FILE_SIZE_LIMIT_DEFAULT),
|
||||
FILE_SIZE_LIMIT_MAX,
|
||||
);
|
||||
multerOptions.limits['fileSize'] = Math.max(FILE_SIZE_LIMIT_MIN, storage.rules.size ?? FILE_SIZE_LIMIT_DEFAULT);
|
||||
|
||||
const upload = multer(multerOptions).single(FILE_FIELD_NAME);
|
||||
try {
|
||||
|
@ -7,13 +7,13 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { uid } from '@nocobase/utils';
|
||||
import iconv from 'iconv-lite';
|
||||
import path from 'path';
|
||||
import { uid } from '@nocobase/utils';
|
||||
|
||||
export function getFilename(req, file, cb) {
|
||||
const originalname = iconv.decode(Buffer.from(file.originalname, 'binary'), 'utf8');
|
||||
const baseName = path.basename(originalname, path.extname(originalname));
|
||||
const originalname = Buffer.from(file.originalname, 'binary').toString('utf8');
|
||||
// Filename in Windows cannot contain the following characters: < > ? * | : " \ /
|
||||
const baseName = path.basename(originalname.replace(/[<>?*|:"\\/]/g, '-'), path.extname(originalname));
|
||||
cb(null, `${baseName}-${uid(6)}${path.extname(originalname)}`);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user