Skip to content

Commit 51bc0dd

Browse files
Copilotwhyour
andcommitted
Support absolute paths like /dev/null for log redirection
Co-authored-by: whyour <[email protected]>
1 parent 71073b8 commit 51bc0dd

File tree

5 files changed

+63
-23
lines changed

5 files changed

+63
-23
lines changed

back/services/cron.ts

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -477,19 +477,33 @@ export default class CronService {
477477
);
478478

479479
let { id, command, log_path, log_name } = cron;
480-
// Sanitize log_name to prevent path traversal
481-
const sanitizedLogName = log_name
482-
? log_name.replace(/[\/\\\.]/g, '_').replace(/^_+|_+$/g, '')
483-
: '';
484-
const uniqPath =
485-
sanitizedLogName || (await getUniqPath(command, `${id}`));
486-
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
487-
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
488-
if (log_path?.split('/')?.every((x) => x !== uniqPath)) {
489-
await fs.mkdir(logDirPath, { recursive: true });
480+
481+
// Check if log_name is an absolute path (e.g., /dev/null)
482+
const isAbsolutePath = log_name && log_name.startsWith('/');
483+
484+
let uniqPath: string;
485+
let absolutePath: string;
486+
let logPath: string;
487+
488+
if (isAbsolutePath) {
489+
// Use absolute path directly for special files like /dev/null
490+
uniqPath = log_name!;
491+
absolutePath = log_name!;
492+
logPath = log_name!;
493+
} else {
494+
// Sanitize log_name to prevent path traversal for relative paths
495+
const sanitizedLogName = log_name
496+
? log_name.replace(/[\/\\\.]/g, '_').replace(/^_+|_+$/g, '')
497+
: '';
498+
uniqPath = sanitizedLogName || (await getUniqPath(command, `${id}`));
499+
const logTime = dayjs().format('YYYY-MM-DD-HH-mm-ss-SSS');
500+
const logDirPath = path.resolve(config.logPath, `${uniqPath}`);
501+
if (log_path?.split('/')?.every((x) => x !== uniqPath)) {
502+
await fs.mkdir(logDirPath, { recursive: true });
503+
}
504+
logPath = `${uniqPath}/${logTime}.log`;
505+
absolutePath = path.resolve(config.logPath, `${logPath}`);
490506
}
491-
const logPath = `${uniqPath}/${logTime}.log`;
492-
const absolutePath = path.resolve(config.logPath, `${logPath}`);
493507
const cp = spawn(
494508
`real_log_path=${logPath} no_delay=true ${this.makeCommand(
495509
cron,

back/validation/schedule.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,21 @@ export const commonCronSchema = {
4141
.optional()
4242
.allow('')
4343
.allow(null)
44-
.pattern(/^[a-zA-Z0-9_-]+$/)
45-
.max(100)
44+
.custom((value, helpers) => {
45+
if (!value) return value;
46+
// Allow absolute paths like /dev/null
47+
if (value.startsWith('/')) {
48+
return value;
49+
}
50+
// For relative names, enforce strict pattern
51+
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
52+
return helpers.error('string.pattern.base');
53+
}
54+
if (value.length > 100) {
55+
return helpers.error('string.max');
56+
}
57+
return value;
58+
})
4659
.messages({
4760
'string.pattern.base': '日志名称只能包含字母、数字、下划线和连字符',
4861
'string.max': '日志名称不能超过100个字符',

src/locales/en-US.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,9 @@
524524
"清除成功": "Clean successful",
525525
"日志名称": "Log Name",
526526
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate",
527+
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate. Supports absolute paths like /dev/null",
527528
"请输入自定义日志文件夹名称": "Please enter a custom log folder name",
529+
"请输入自定义日志文件夹名称或绝对路径": "Please enter a custom log folder name or absolute path",
528530
"日志名称只能包含字母、数字、下划线和连字符": "Log name can only contain letters, numbers, underscores and hyphens",
529531
"日志名称不能超过100个字符": "Log name cannot exceed 100 characters"
530532
}

src/locales/zh-CN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,9 @@
524524
"清除成功": "清除成功",
525525
"日志名称": "日志名称",
526526
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成",
527+
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null",
527528
"请输入自定义日志文件夹名称": "请输入自定义日志文件夹名称",
529+
"请输入自定义日志文件夹名称或绝对路径": "请输入自定义日志文件夹名称或绝对路径",
528530
"日志名称只能包含字母、数字、下划线和连字符": "日志名称只能包含字母、数字、下划线和连字符",
529531
"日志名称不能超过100个字符": "日志名称不能超过100个字符"
530532
}

src/pages/crontab/modal.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,22 +184,31 @@ const CronModal = ({
184184
name="log_name"
185185
label={intl.get('日志名称')}
186186
tooltip={intl.get(
187-
'自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成',
187+
'自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null',
188188
)}
189189
rules={[
190190
{
191-
pattern: /^[a-zA-Z0-9_-]*$/,
192-
message: intl.get('日志名称只能包含字母、数字、下划线和连字符'),
193-
},
194-
{
195-
max: 100,
196-
message: intl.get('日志名称不能超过100个字符'),
191+
validator: (_, value) => {
192+
if (!value) return Promise.resolve();
193+
// Allow absolute paths
194+
if (value.startsWith('/')) return Promise.resolve();
195+
// For relative names, enforce strict pattern
196+
if (!/^[a-zA-Z0-9_-]+$/.test(value)) {
197+
return Promise.reject(
198+
intl.get('日志名称只能包含字母、数字、下划线和连字符'),
199+
);
200+
}
201+
if (value.length > 100) {
202+
return Promise.reject(intl.get('日志名称不能超过100个字符'));
203+
}
204+
return Promise.resolve();
205+
},
197206
},
198207
]}
199208
>
200209
<Input
201-
placeholder={intl.get('请输入自定义日志文件夹名称')}
202-
maxLength={100}
210+
placeholder={intl.get('请输入自定义日志文件夹名称或绝对路径')}
211+
maxLength={200}
203212
/>
204213
</Form.Item>
205214
<Form.Item

0 commit comments

Comments
 (0)