mirror of
https://github.com/whyour/qinglong.git
synced 2026-11-03 16:19:11 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
301efa9e72 | ||
|
|
5eaff6ba90 | ||
|
|
855f591992 | ||
|
|
d30eb2008c | ||
|
|
e1ce0f3fa9 | ||
|
|
82514e65e1 | ||
|
|
31261190f0 |
+23
-2
@@ -35,6 +35,23 @@ function isPathAllowed(targetPath: string): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeScriptFile(
|
||||||
|
targetPath: string,
|
||||||
|
content: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const target = resolve(targetPath);
|
||||||
|
for (const writableRoot of config.writePathList) {
|
||||||
|
const root = resolve(writableRoot);
|
||||||
|
const prefix = root.endsWith(sep) ? root : root + sep;
|
||||||
|
// Check the actual write target, including filename, at the write boundary.
|
||||||
|
if (!target.startsWith(prefix)) continue;
|
||||||
|
if (!isPathAllowed(target)) return false;
|
||||||
|
await writeFileWithLock(target, content);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
async function existingWritableRealPath(targetPath: string): Promise<string> {
|
async function existingWritableRealPath(targetPath: string): Promise<string> {
|
||||||
if (!isPathAllowed(targetPath)) {
|
if (!isPathAllowed(targetPath)) {
|
||||||
throw new ScriptHistoryError(403, '暂无权限');
|
throw new ScriptHistoryError(403, '暂无权限');
|
||||||
@@ -373,7 +390,9 @@ export default (app: Router) => {
|
|||||||
await rmPath(originFilePath);
|
await rmPath(originFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await writeFileWithLock(filePath, content);
|
if (!(await writeScriptFile(filePath, content))) {
|
||||||
|
return res.send({ code: 403, message: t('暂无权限') });
|
||||||
|
}
|
||||||
return res.send({ code: 200 });
|
return res.send({ code: 200 });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return historyError(e, res, next);
|
return historyError(e, res, next);
|
||||||
@@ -513,7 +532,9 @@ export default (app: Router) => {
|
|||||||
if (!isPathAllowed(filePath)) {
|
if (!isPathAllowed(filePath)) {
|
||||||
return res.send({ code: 403, message: t('暂无权限') });
|
return res.send({ code: 403, message: t('暂无权限') });
|
||||||
}
|
}
|
||||||
await writeFileWithLock(filePath, content || '');
|
if (!(await writeScriptFile(filePath, content || ''))) {
|
||||||
|
return res.send({ code: 403, message: t('暂无权限') });
|
||||||
|
}
|
||||||
|
|
||||||
const scriptService = Container.get(ScriptService);
|
const scriptService = Container.get(ScriptService);
|
||||||
const result = await scriptService.runScript(filePath);
|
const result = await scriptService.runScript(filePath);
|
||||||
|
|||||||
+24
-8
@@ -12,7 +12,7 @@ export class HttpServerService {
|
|||||||
async initialize(expressApp: express.Application, port: number) {
|
async initialize(expressApp: express.Application, port: number) {
|
||||||
const hostsToTry = [
|
const hostsToTry = [
|
||||||
config.bindHost,
|
config.bindHost,
|
||||||
...(config.bindHost === '::' ? ['0.0.0.0'] : [])
|
...(config.bindHost === '::' ? ['0.0.0.0'] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
let lastError: Error | null = null;
|
let lastError: Error | null = null;
|
||||||
@@ -20,16 +20,21 @@ export class HttpServerService {
|
|||||||
for (const host of hostsToTry) {
|
for (const host of hostsToTry) {
|
||||||
try {
|
try {
|
||||||
const server = await this.tryListen(expressApp, port, host);
|
const server = await this.tryListen(expressApp, port, host);
|
||||||
Logger.debug(`[boot] HTTP service started successfully on ${host}:${port}`);
|
Logger.debug(
|
||||||
|
`[boot] HTTP service started successfully on ${host}:${port}`,
|
||||||
|
);
|
||||||
metricsService.record('http_service_start', 1, {
|
metricsService.record('http_service_start', 1, {
|
||||||
port: port.toString(),
|
port: port.toString(),
|
||||||
host
|
host,
|
||||||
});
|
});
|
||||||
this.server = server;
|
this.server = server;
|
||||||
return server;
|
return server;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
lastError = err as Error;
|
lastError = err as Error;
|
||||||
Logger.warn(`Failed to bind HTTP on ${host}:${port}, trying next...`, err);
|
Logger.warn(
|
||||||
|
`Failed to bind HTTP on ${host}:${port}, trying next...`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,17 +42,28 @@ export class HttpServerService {
|
|||||||
throw lastError || new Error('Failed to start HTTP service');
|
throw lastError || new Error('Failed to start HTTP service');
|
||||||
}
|
}
|
||||||
|
|
||||||
private async tryListen(expressApp: express.Application, port: number, host: string): Promise<Server> {
|
private async tryListen(
|
||||||
|
expressApp: express.Application,
|
||||||
|
port: number,
|
||||||
|
host: string,
|
||||||
|
): Promise<Server> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// There is one HTTP worker; accepting here avoids primary IPC handoff
|
// There is one HTTP worker; accepting here avoids primary IPC handoff
|
||||||
// for every connection. Restore shared listening for custom clusters.
|
// for every connection. Restore shared listening for custom clusters.
|
||||||
const server = expressApp.listen({
|
const server = expressApp.listen(
|
||||||
|
{
|
||||||
port,
|
port,
|
||||||
host,
|
host,
|
||||||
exclusive: process.env.QL_HTTP_SHARED_LISTEN !== 'true',
|
exclusive: process.env.QL_HTTP_SHARED_LISTEN !== 'true',
|
||||||
}, () => {
|
},
|
||||||
|
() => {
|
||||||
resolve(server);
|
resolve(server);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
server.requestTimeout = 300000;
|
||||||
|
server.headersTimeout = 120000;
|
||||||
|
server.keepAliveTimeout = 65000;
|
||||||
|
|
||||||
server.on('error', (err: Error) => {
|
server.on('error', (err: Error) => {
|
||||||
server.close();
|
server.close();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export async function writeFileWithLock(
|
|||||||
}
|
}
|
||||||
if (!(await fileExist(filePath))) {
|
if (!(await fileExist(filePath))) {
|
||||||
const fileHandle = await open(filePath, 'w');
|
const fileHandle = await open(filePath, 'w');
|
||||||
fileHandle.close();
|
await fileHandle.close();
|
||||||
}
|
}
|
||||||
const lockfilePath = getUniqueLockPath(filePath);
|
const lockfilePath = getUniqueLockPath(filePath);
|
||||||
|
|
||||||
@@ -34,9 +34,12 @@ export async function writeFileWithLock(
|
|||||||
},
|
},
|
||||||
lockfilePath,
|
lockfilePath,
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
await writeFile(filePath, content, { encoding: 'utf8', ...options });
|
await writeFile(filePath, content, { encoding: 'utf8', ...options });
|
||||||
if (options?.mode) {
|
if (options?.mode) {
|
||||||
await chmod(filePath, options.mode);
|
await chmod(filePath, options.mode);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
await release();
|
await release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const test = require('node:test'),
|
||||||
|
assert = require('node:assert/strict'),
|
||||||
|
fs = require('node:fs'),
|
||||||
|
os = require('node:os'),
|
||||||
|
path = require('node:path');
|
||||||
|
const load = require('../helpers/load-security-module.cjs');
|
||||||
|
const { writeFileWithLock, getUniqueLockPath } = load('back/shared/utils.ts', {
|
||||||
|
'../config/util': { fileExist: async (p) => fs.existsSync(p) },
|
||||||
|
});
|
||||||
|
test('write failure releases the lock and preserves the original error', async (t) => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-lock-review-'));
|
||||||
|
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
||||||
|
const p = path.join(dir, 'script');
|
||||||
|
fs.writeFileSync(p, 'original');
|
||||||
|
await assert.rejects(
|
||||||
|
writeFileWithLock(p, 'bad', { encoding: 'invalid-encoding' }),
|
||||||
|
(e) => e.code === 'ERR_INVALID_ARG_VALUE',
|
||||||
|
);
|
||||||
|
assert.equal(fs.existsSync(getUniqueLockPath(p)), false);
|
||||||
|
await writeFileWithLock(p, 'retry');
|
||||||
|
assert.equal(fs.readFileSync(p, 'utf8'), 'retry');
|
||||||
|
});
|
||||||
|
test('first write closes its file handle and keeps requested permissions', async (t) => {
|
||||||
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ql-lock-review-'));
|
||||||
|
t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
|
||||||
|
const p = path.join(dir, 'new');
|
||||||
|
await writeFileWithLock(p, 'created', { mode: 0o600 });
|
||||||
|
assert.equal(fs.readFileSync(p, 'utf8'), 'created');
|
||||||
|
assert.equal(fs.statSync(p).mode & 0o777, 0o600);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user