增加现场提货的配置
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { isXfxProviderCode } from './fulfillment-provider';
|
||||
import { isXfxProviderCode, sanitizeDeliveryHintHtml } from './fulfillment-provider';
|
||||
|
||||
describe('isXfxProviderCode', () => {
|
||||
it('匹配标准编码与城市前缀', () => {
|
||||
@@ -16,3 +16,40 @@ describe('isXfxProviderCode', () => {
|
||||
expect(isXfxProviderCode('SF')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizeDeliveryHintHtml', () => {
|
||||
const styled =
|
||||
'<span style="color:#A61D24;font-weight:700;font-size:13px">同城配送,预计24小时内送到</span>';
|
||||
|
||||
it('保留允许的 style', () => {
|
||||
expect(sanitizeDeliveryHintHtml(styled)).toBe(styled);
|
||||
});
|
||||
|
||||
it('解码 " 后保留 style', () => {
|
||||
const raw =
|
||||
'<span style="color:#A61D24;font-weight:700;font-size:13px">同城配送,预计24小时内送到</span><br />\n<span>13点之前下单,当日送达</span>';
|
||||
expect(sanitizeDeliveryHintHtml(raw)).toBe(
|
||||
`${styled}<br/>\n<span>13点之前下单,当日送达</span>`,
|
||||
);
|
||||
});
|
||||
|
||||
it('弯引号与无引号 style 也能保留', () => {
|
||||
expect(
|
||||
sanitizeDeliveryHintHtml(
|
||||
'<span style=\u201Ccolor:#A61D24;font-weight:700;font-size:13px\u201D>同城配送,预计24小时内送到</span>',
|
||||
),
|
||||
).toBe(styled);
|
||||
expect(
|
||||
sanitizeDeliveryHintHtml(
|
||||
'<span style=color:#A61D24;font-weight:700;font-size:13px>同城配送,预计24小时内送到</span>',
|
||||
),
|
||||
).toBe(styled);
|
||||
});
|
||||
|
||||
it('仍去掉脚本与非法样式', () => {
|
||||
expect(sanitizeDeliveryHintHtml('<span style="color:red;background:url(x)">x</span>')).toBe(
|
||||
'<span style="color:red">x</span>',
|
||||
);
|
||||
expect(sanitizeDeliveryHintHtml('<script>alert(1)</script><span>ok</span>')).toBe('<span>ok</span>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -138,6 +138,29 @@ export type LocalDeliveryDto = {
|
||||
hintHtml: string | null;
|
||||
};
|
||||
|
||||
/** 粘贴 HTML 源码时常带 " / 弯引号;不解码则 style 正则匹配失败,整段样式被剥掉 */
|
||||
function decodeHintHtmlEntities(html: string): string {
|
||||
let prev = '';
|
||||
let out = html;
|
||||
for (let i = 0; i < 3 && out !== prev; i += 1) {
|
||||
prev = out;
|
||||
out = out
|
||||
.replace(/&/gi, '&')
|
||||
.replace(/"/gi, '"')
|
||||
.replace(/�*34;/g, '"')
|
||||
.replace(/�*22;/gi, '"')
|
||||
.replace(/'/gi, "'")
|
||||
.replace(/�*39;/g, "'")
|
||||
.replace(/�*27;/gi, "'")
|
||||
.replace(/</gi, '<')
|
||||
.replace(/>/gi, '>')
|
||||
.replace(/ /gi, ' ');
|
||||
}
|
||||
return out
|
||||
.replace(/[\u201c\u201d\u201e\u00ab\u00bb]/g, '"')
|
||||
.replace(/[\u2018\u2019]/g, "'");
|
||||
}
|
||||
|
||||
function sanitizeHintStyle(raw: string): string {
|
||||
return raw
|
||||
.split(';')
|
||||
@@ -156,6 +179,16 @@ function sanitizeHintStyle(raw: string): string {
|
||||
.join(';');
|
||||
}
|
||||
|
||||
function extractHintAttr(attrs: string, name: string): string {
|
||||
const re = new RegExp(
|
||||
`\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`,
|
||||
'i',
|
||||
);
|
||||
const match = attrs.match(re);
|
||||
if (!match) return '';
|
||||
return (match[1] ?? match[2] ?? match[3] ?? '').trim();
|
||||
}
|
||||
|
||||
/** 文本换行转成 br,供 C 端 RichText 使用(不改 HQ 存盘原文) */
|
||||
export function deliveryHintHtmlToRichNodes(html: string): string {
|
||||
return html
|
||||
@@ -171,6 +204,7 @@ export function sanitizeDeliveryHintHtml(raw?: string | null): string | null {
|
||||
if (html.length > LOCAL_DELIVERY_HINT_MAX_LEN) {
|
||||
html = html.slice(0, LOCAL_DELIVERY_HINT_MAX_LEN);
|
||||
}
|
||||
html = decodeHintHtmlEntities(html);
|
||||
html = html.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, '');
|
||||
html = html.replace(/on[a-z]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, '');
|
||||
html = html.replace(/javascript\s*:/gi, '');
|
||||
@@ -180,18 +214,12 @@ export function sanitizeDeliveryHintHtml(raw?: string | null): string | null {
|
||||
if (!HINT_ALLOWED_TAGS.has(name)) return '';
|
||||
if (name === 'br') return closing ? '' : '<br/>';
|
||||
if (closing) return `</${name}>`;
|
||||
let style = '';
|
||||
const styleMatch = String(attrs).match(/\sstyle\s*=\s*("([^"]*)"|'([^']*)')/i);
|
||||
if (styleMatch) {
|
||||
style = sanitizeHintStyle(styleMatch[2] ?? styleMatch[3] ?? '');
|
||||
}
|
||||
const style = sanitizeHintStyle(extractHintAttr(String(attrs), 'style'));
|
||||
let color = '';
|
||||
let size = '';
|
||||
if (name === 'font') {
|
||||
const colorMatch = String(attrs).match(/\scolor\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/i);
|
||||
if (colorMatch) color = (colorMatch[2] ?? colorMatch[3] ?? colorMatch[4] ?? '').trim();
|
||||
const sizeMatch = String(attrs).match(/\ssize\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/i);
|
||||
if (sizeMatch) size = (sizeMatch[2] ?? sizeMatch[3] ?? sizeMatch[4] ?? '').trim();
|
||||
color = extractHintAttr(String(attrs), 'color');
|
||||
size = extractHintAttr(String(attrs), 'size');
|
||||
}
|
||||
const extra: string[] = [];
|
||||
if (style) extra.push(`style="${style}"`);
|
||||
|
||||
Reference in New Issue
Block a user