安全的用户输入占位符替换:解决Dehydrate/Rehydrate冲突问题
实现安全的Dehydrate/Rehydrate(脱水/重水合)功能
功能需求
实现Dehydrate(脱水):将用户输入文本中指定的目标字符串替换为占位符;后续执行Rehydrate(重水合),用新字符串替换占位符,还原内容结构。
示例:
原始文本:
Visit [my page](http://example.com/posts/)
脱水时,将$search='http://example.com'替换为%WEBSITE_URL%,得到:
Visit the page [my page](%WEBSITE_URL%/posts/)
重水合时,用$replace='http://another-site.net'替换占位符,得到:
Visit the page [my page](http://another-site.net/posts/)
朴素解决方案的问题
最初直接用字符串替换的实现存在明显漏洞:
$search = 'http://example.com'; $dehydrated = str_replace($search, '%WEBSITE_URL%', $text); // 重水合反向操作 $replace = 'http://another-site.net'; $rehydrated = str_replace('%WEBSITE_URL%', $replace, $dehydrated);
如果用户输入包含字面量%WEBSITE_URL%,重水合时会被错误替换。比如:
输入文本:
$text = 'Visit [my page](http://example.com/posts/). Placeholders are %WEBSITE_URL%';
错误结果:
Visit [my page](http://another-site.net/posts/). Placeholders are http://another-site.net
正确结果应该保留原生占位符:
Visit [my page](http://another-site.net/posts/). Placeholders are %WEBSITE_URL%
改进方案的局限性
为解决上述问题,改进方案先转义所有%为%%,再替换目标字符串,重水合时用正则匹配未转义的占位符:
// 脱水:先转义%,再替换目标字符串 $search = 'http://example.com'; $dehydrated = str_replace(['%', $search], ['%%', '%WEBSITE_URL%'], $text); // 重水合:匹配未被转义的%WEBSITE_URL%,再恢复%%为% $replace = 'http://another-site.net'; $rehydrated = preg_replace('/%WEBSITE_URL%(?!%)/', $replace, $dehydrated); $rehydrated = str_replace('%%', '%', $rehydrated);
但该方案仍有漏洞:若用户输入%http://example.com%,脱水后会变成%%WEBSITE_URL%%,重水合后会被还原为%http://another-site.net%,而非用户原本输入的%http://example.com%。
最终安全解决方案
要彻底避免冲突,核心思路是让占位符与用户可能输入的内容完全隔离,同时明确区分目标字符串和原生特殊标记。
方案一:唯一占位符+转义机制
使用不易被用户输入的占位符格式,配合转义处理原生占位符:
// 定义唯一占位符,采用双下划线包裹降低冲突概率 define('PLACEHOLDER', '__WEBSITE_URL__'); // 脱水函数 function dehydrate(string $text, string $search): string { // 转义文本中已存在的占位符,避免混淆 $text = str_replace(PLACEHOLDER, '\\' . PLACEHOLDER, $text); // 精确匹配目标字符串并替换为占位符(preg_quote处理正则特殊字符) $searchPattern = '/' . preg_quote($search, '/') . '/'; return preg_replace($searchPattern, PLACEHOLDER, $text); } // 重水合函数 function rehydrate(string $dehydratedText, string $replace): string { // 仅替换未被转义的占位符 $replacePattern = '/(?<!\\\\)' . preg_quote(PLACEHOLDER, '/') . '/'; $text = preg_replace($replacePattern, $replace, $dehydratedText); // 恢复被转义的原生占位符 return str_replace('\\' . PLACEHOLDER, PLACEHOLDER, $text); } // 测试示例 $text = 'Visit [my page](http://example.com/posts/), %http://example.com%, __WEBSITE_URL__'; $search = 'http://example.com'; $replace = 'http://another-site.net'; $dehydrated = dehydrate($text, $search); // 脱水结果:Visit [my page](__WEBSITE_URL__/posts/), %__WEBSITE_URL__%, \__WEBSITE_URL__ $rehydrated = rehydrate($dehydrated, $replace); // 重水合结果:Visit [my page](http://another-site.net/posts/), %http://another-site.net%, __WEBSITE_URL__
该方案通过转义原生占位符、精确匹配未转义的目标占位符,彻底避免误替换。
方案二:随机UUID作为临时占位符
如果要完全消除占位符冲突的可能性,可使用随机UUID作为临时占位符(全局唯一,用户输入不可能匹配):
function dehydrate(string $text, string $search): array { $placeholder = 'UUID_' . uuid_create(UUID_TYPE_RANDOM); // 转义可能存在的临时占位符(概率极低) $text = str_replace($placeholder, '\\' . $placeholder, $text); $dehydrated = str_replace($search, $placeholder, $text); return [ 'text' => $dehydrated, 'placeholder' => $placeholder ]; } function rehydrate(string $dehydratedText, string $replace, string $placeholder): string { $text = str_replace($placeholder, $replace, $dehydratedText); return str_replace('\\' . $placeholder, $placeholder, $text); } // 使用方式 $result = dehydrate($text, $search); $rehydrated = rehydrate($result['text'], $replace, $result['placeholder']);
这种方案从根源上避免了占位符冲突,适合对安全性要求极高的场景。
内容的提问来源于stack exchange,提问作者quantumSoup
相关产品推荐
相关产品推荐

