fix: validate HTTP dates IPv6 URLs and parser errors
CSS quality / css-quality (push) Has been cancelled
CSS quality / css-quality (push) Has been cancelled
This commit is contained in:
@@ -26,7 +26,7 @@ import { SendChainService } from '../send-chain/send-chain.service';
|
||||
import { decryptSecret, encryptSecret } from './open-api.crypto';
|
||||
import type { OpenApiAuthContext } from './open-api.types';
|
||||
import { ProtocolLogsService } from '../protocol-logs/protocol-logs.service';
|
||||
import { pinnedWebhookLookup, publicOpenApiFailure, webhookJobId } from './open-api.protocol';
|
||||
import { parseOpenApiDate, pinnedWebhookLookup, publicOpenApiFailure, webhookJobId } from './open-api.protocol';
|
||||
import { automaticDeliveryMode } from './delivery-mode';
|
||||
|
||||
export const OPEN_API_WEBHOOK_TRANSPORT = Symbol('open-api-webhook-transport');
|
||||
@@ -449,8 +449,9 @@ export class OpenApiService implements OnModuleInit, OnModuleDestroy {
|
||||
if (value !== undefined && typeof value !== 'string')
|
||||
throw new BadRequestException({ code: 'PARAMETER_INVALID', message: '查询参数必须为单个字符串' });
|
||||
}
|
||||
const endTime = query.endTime ? new Date(query.endTime) : new Date();
|
||||
const startTime = query.startTime ? new Date(query.startTime) : new Date(endTime.getTime() - 24 * 3600_000);
|
||||
const endTime = query.endTime !== undefined ? parseOpenApiDate(query.endTime) : new Date();
|
||||
const startTime =
|
||||
query.startTime !== undefined ? parseOpenApiDate(query.startTime) : new Date(endTime.getTime() - 24 * 3600_000);
|
||||
if (!Number.isFinite(startTime.getTime()) || !Number.isFinite(endTime.getTime()) || startTime > endTime)
|
||||
throw new BadRequestException({ code: 'TIME_RANGE_INVALID', message: '查询时间范围非法' });
|
||||
if (endTime.getTime() - startTime.getTime() > auth.config.maxQueryRangeDays * 86400_000)
|
||||
@@ -901,7 +902,7 @@ async function validateWebhookUrl(value: string, requireHttps: boolean) {
|
||||
return (await resolveWebhookTarget(value, requireHttps)).url.toString();
|
||||
}
|
||||
|
||||
async function resolveWebhookTarget(value: string, requireHttps: boolean) {
|
||||
export async function resolveWebhookTarget(value: string, requireHttps: boolean) {
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(String(value ?? '').trim());
|
||||
@@ -911,7 +912,13 @@ async function resolveWebhookTarget(value: string, requireHttps: boolean) {
|
||||
if (!['http:', 'https:'].includes(url.protocol)) throw new BadRequestException('Webhook仅支持HTTP/HTTPS');
|
||||
if (requireHttps && url.protocol !== 'https:') throw new BadRequestException('当前应用要求Webhook使用HTTPS');
|
||||
if (url.username || url.password) throw new BadRequestException('Webhook URL不能包含用户名或密码');
|
||||
const addresses = isIP(url.hostname) ? [{ address: url.hostname }] : await lookup(url.hostname, { all: true });
|
||||
const hostname = url.hostname.startsWith('[') ? url.hostname.slice(1, -1) : url.hostname;
|
||||
let addresses: Array<{ address: string }>;
|
||||
try {
|
||||
addresses = isIP(hostname) ? [{ address: hostname }] : await lookup(hostname, { all: true });
|
||||
} catch {
|
||||
throw new BadRequestException('Webhook域名未解析到可用地址');
|
||||
}
|
||||
if (addresses.some(({ address }) => isPrivateAddress(address)))
|
||||
throw new BadRequestException('Webhook URL不能指向内网、环回或链路本地地址');
|
||||
const selected = addresses[0];
|
||||
@@ -958,7 +965,14 @@ async function postWebhook(
|
||||
}
|
||||
|
||||
function isPrivateAddress(address: string) {
|
||||
const normalized = address.replace(/^::ffff:/, '');
|
||||
const canonical = isIP(address) === 6 ? new URL(`http://[${address}]`).hostname.slice(1, -1) : address;
|
||||
const mapped = /^::ffff:([a-f0-9]{1,4}):([a-f0-9]{1,4})$/i.exec(canonical);
|
||||
if (mapped) {
|
||||
const high = parseInt(mapped[1], 16),
|
||||
low = parseInt(mapped[2], 16);
|
||||
return isPrivateAddress(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
|
||||
}
|
||||
const normalized = canonical.toLowerCase();
|
||||
if (
|
||||
normalized === '::1' ||
|
||||
normalized === '::' ||
|
||||
@@ -994,7 +1008,7 @@ function decodeCursor(value?: string) {
|
||||
if (!Array.isArray(parsed) || parsed.length !== 2 || typeof parsed[0] !== 'string' || typeof parsed[1] !== 'string')
|
||||
throw new Error();
|
||||
const [date, id] = parsed;
|
||||
const receivedAt = new Date(date);
|
||||
const receivedAt = parseOpenApiDate(date);
|
||||
if (!id || !Number.isFinite(receivedAt.getTime())) throw new Error();
|
||||
return { receivedAt, id };
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user