Archived
mail domain:解析结果 → 入站记录 / 视图,附件与原件按上限分存留注,厂商原话脱敏三形
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 脱敏 —— 凡要落库 / 上接口的厂商原话都先过这里(设计稿 §9.4)。
|
||||
* 明文、base64(SMTP AUTH 的形)、URL 编码三形都换掉。
|
||||
*/
|
||||
const MIN_SECRET_CHARS = 4;
|
||||
|
||||
export function secretVariants(secret: string): string[] {
|
||||
if (secret.length < MIN_SECRET_CHARS) return [];
|
||||
const variants = [secret, Buffer.from(secret, 'utf8').toString('base64'), encodeURIComponent(secret)];
|
||||
return [...new Set(variants.filter((v) => v.length >= MIN_SECRET_CHARS))];
|
||||
}
|
||||
|
||||
function escapeRegex(s: string): string {
|
||||
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
export function redactSecrets(text: string, secrets: readonly string[]): string {
|
||||
let out = text;
|
||||
for (const secret of secrets) {
|
||||
for (const variant of secretVariants(secret)) {
|
||||
out = out.replace(new RegExp(escapeRegex(variant), 'g'), '***');
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 错误 → 一句话;nodemailer / imapflow 把服务器回话挂在 `response` / `responseText` 上,一并带上 */
|
||||
export function errorText(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
const bag = error as Error & { response?: unknown; responseText?: unknown };
|
||||
const response = typeof bag.response === 'string' ? bag.response : typeof bag.responseText === 'string' ? bag.responseText : '';
|
||||
return response === '' || response === error.message ? error.message : `${error.message} —— ${response}`;
|
||||
}
|
||||
if (error === undefined || error === null) return '未知错误';
|
||||
return String(error);
|
||||
}
|
||||
Reference in New Issue
Block a user