feat(util): add URL sanitization utility and improve redirect handling
1. add cleanParamUrls recursive URL sanitization util 2. add URL cleanup to diyRedirectTo as final defense 3. refactor redirectTo to avoid duplicate cleanup 4. add comprehensive unit tests for all new functionality
This commit is contained in:
@@ -691,6 +691,49 @@ export default {
|
||||
/** 把 page_params 还原成对象(JSON.parse 的安全封装) */
|
||||
parsePageParams,
|
||||
|
||||
/**
|
||||
* 递归清理任意对象/数组中所有"看起来像 URL"的字符串字段:
|
||||
* - 去除反引号 / 各种空白 / 零宽字符
|
||||
* - 修复 scheme 后多余斜杠(https:////host → https://host)
|
||||
*
|
||||
* "看起来像 URL" 的判定: 含 `://`(大小写不敏感)
|
||||
* - 命中: 走 cleanUrlString
|
||||
* - 不命中: 原样保留(name / title / 标识符等普通字符串字段不被误伤)
|
||||
*
|
||||
* 设计: 不限定 key 列表,递归遍历,新加任何 URL 字段都自动覆盖
|
||||
*
|
||||
* @param {*} value 任意值
|
||||
* @returns {*} 与输入同结构的清理后值
|
||||
*/
|
||||
cleanParamUrls(value) {
|
||||
// 内部递归函数,通过闭包引用自身,避免 this 绑定问题
|
||||
const _clean = (v) => {
|
||||
if (v === null || v === undefined) return v;
|
||||
if (typeof v === 'string') {
|
||||
if (/:\/\//i.test(v)) {
|
||||
return v
|
||||
.split('').filter(ch =>
|
||||
ch !== '`' && ch !== ' ' && ch !== '\t' && ch !== '\n' && ch !== '\r',
|
||||
).join('')
|
||||
.replace(/^(https?:\/\/)\/+/i, '$1');
|
||||
}
|
||||
return v;
|
||||
}
|
||||
if (Array.isArray(v)) {
|
||||
return v.map(item => _clean(item));
|
||||
}
|
||||
if (typeof v === 'object') {
|
||||
const out = {};
|
||||
for (const k of Object.keys(v)) {
|
||||
out[k] = _clean(v[k]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
return v;
|
||||
};
|
||||
return _clean(value);
|
||||
},
|
||||
|
||||
/** 判断给定路径是否是 TabBar 根页面(用于动态控制返回按钮显示) */
|
||||
isTabBarRootPage,
|
||||
|
||||
@@ -704,10 +747,12 @@ export default {
|
||||
* @param {string} mode 模式 navigateTo、redirectTo、reLaunch、tabbar, 默认navigateTo
|
||||
*/
|
||||
redirectTo(to, param = {}, mode = 'navigateTo') {
|
||||
// 注意: URL 清理只在 diyRedirectTo 入口做(更上游,职责清晰)。
|
||||
// redirectTo 接收到的 param 应当是干净的;不重复清理,避免性能浪费 + 误伤边界。
|
||||
//
|
||||
// 1. 拆分 path 与已有 query,避免后面 indexOf('?') 误判
|
||||
// #ifdef MP-HARMONY
|
||||
console.log('[navigate][HARMONY] redirectTo entry: to=', to, 'mode=', mode, 'param=', param);
|
||||
// #endif
|
||||
console.log('[navigate] redirectTo entry: to=', to, 'mode=', mode, 'param=', JSON.stringify(param));
|
||||
|
||||
const queryIdx = to.indexOf('?');
|
||||
let path = queryIdx === -1 ? to : to.substring(0, queryIdx);
|
||||
const existingParams = {};
|
||||
@@ -725,8 +770,8 @@ export default {
|
||||
|
||||
// 2. 合并 param:同 key 时新值覆盖旧值,杜绝 ?name=A&name=B 这种重复
|
||||
// page_params 永远放在 URL 最末:从 param 中抽离后追加到 merged 末尾
|
||||
const safeParam = param || {};
|
||||
const { page_params, ...restParam } = safeParam;
|
||||
// param 已在函数顶部 0 号步骤做了 URL 清理,这里直接用
|
||||
const { page_params, ...restParam } = param;
|
||||
const merged = Object.assign({}, existingParams, restParam);
|
||||
if (page_params !== undefined) {
|
||||
merged.page_params = page_params;
|
||||
@@ -770,11 +815,15 @@ export default {
|
||||
.join('&');
|
||||
|
||||
let url = path + (queryStr ? '?' + queryStr : '');
|
||||
console.log(`[redirectTo]0 url= ${url}`, {
|
||||
to,
|
||||
param
|
||||
});
|
||||
|
||||
// 替换url中的前缀
|
||||
url = adaptSubpackageUrl(url);
|
||||
|
||||
console.log('[redirectTo] to=', to, 'param=', param, '-> url=', url);
|
||||
console.log('[redirectTo]1 to=', to, 'param=', param, '-> url=', url);
|
||||
|
||||
// 针对tabBar.list中的路径,直接切换tabBar
|
||||
for (let i = 0; i < systemTabBarList.length; i++) {
|
||||
@@ -853,6 +902,8 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`====>mode=${mode}, url=${url}`);
|
||||
|
||||
// 如果url不包含tabBarList中的路径,根据mode判断跳转方式
|
||||
switch (mode) {
|
||||
case 'tabbar':
|
||||
@@ -882,6 +933,7 @@ export default {
|
||||
url
|
||||
});
|
||||
break;
|
||||
case 'navigateTo':
|
||||
default:
|
||||
// 保留当前页面,跳转到应用内的某个页面,新增页面到栈顶,长度+1(上限10层)
|
||||
uni.navigateTo({
|
||||
@@ -1321,6 +1373,14 @@ export default {
|
||||
diyRedirectTo(link) {
|
||||
console.log(`-----> diyRedirectTo`, JSON.stringify(link));
|
||||
//if (link == null || Object.keys(link).length == 1) return;
|
||||
|
||||
// 0. 兜底清理 link 里所有"看起来像 URL"的字符串字段
|
||||
// 递归遍历,任意 key / 任意嵌套层级都被命中
|
||||
// (去除反引号 / 各种空白 / 零宽字符,修复 scheme 后多余斜杠)
|
||||
// 这是最后一道防线,防止后台脏数据(如 wap_url 字段被粘贴时带 markdown 反引号)
|
||||
// 把脏 URL 一直传到 web-view,导致微信 web-view 解析失败
|
||||
link = this.cleanParamUrls(link) || {};
|
||||
|
||||
if (link.wap_url) {
|
||||
link.wap_url = decodeEntity(link.wap_url);
|
||||
}
|
||||
@@ -1353,8 +1413,8 @@ export default {
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
console.log(`外部链接,跳转到webview`, {...link, src: url});
|
||||
this.redirectTo('/pages_tool/webview/webview', {
|
||||
console.log(`外部链接,跳转到webview`, url);
|
||||
this.redirectTo(PAGE_KEY_MAP.PAGE_WEBVIEW, {
|
||||
...link,
|
||||
src: url
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user