转义转义字符

9

我想要模拟 PHP 5.3.0 中实现的 json_encode 位掩码标志,这是我拥有的字符串:

$s = addslashes('O\'Rei"lly'); // O\'Rei\"lly

使用json_encode($s, JSON_HEX_APOS | JSON_HEX_QUOT)输出如下:

"O\\\u0027Rei\\\u0022lly"

我目前正在使用PHP 5.3.0以前的版本进行以下操作:

str_replace(array('\\"', "\\'"), array('\\u0022', '\\\u0027'), json_encode($s))
or
str_replace(array('\\"', '\\\''), array('\\u0022', '\\\u0027'), json_encode($s))

哪个能正确输出相同的结果:

"O\\\u0027Rei\\\u0022lly"

我有些困惑,不明白为什么需要将单引号 ('\\\'' 或者 "\\'" [排除引号]) 替换成 '\\\u0027' 而不是 '\\u0027'
这里是我正在尝试移植到 PHP < 5.3 的代码:
if (get_magic_quotes_gpc() && version_compare(PHP_VERSION, '6.0.0', '<'))
{
    /* JSON_HEX_APOS and JSON_HEX_QUOT are availiable */
    if (version_compare(PHP_VERSION, '5.3.0', '>=') === true)
    {
        $_GET = json_encode($_GET, JSON_HEX_APOS | JSON_HEX_QUOT);
        $_POST = json_encode($_POST, JSON_HEX_APOS | JSON_HEX_QUOT);
        $_COOKIE = json_encode($_COOKIE, JSON_HEX_APOS | JSON_HEX_QUOT);
        $_REQUEST = json_encode($_REQUEST, JSON_HEX_APOS | JSON_HEX_QUOT);
    }

    /* mimic the behaviour of JSON_HEX_APOS and JSON_HEX_QUOT */
    else if (extension_loaded('json') === true)
    {
        $_GET = str_replace(array(), array('\\u0022', '\\u0027'), json_encode($_GET));
        $_POST = str_replace(array(), array('\\u0022', '\\u0027'), json_encode($_POST));
        $_COOKIE = str_replace(array(), array('\\u0022', '\\u0027'), json_encode($_COOKIE));
        $_REQUEST = str_replace(array(), array('\\u0022', '\\u0027'), json_encode($_REQUEST));
    }

    $_GET = json_decode(stripslashes($_GET));
    $_POST = json_decode(stripslashes($_POST));
    $_COOKIE = json_decode(stripslashes($_COOKIE));
    $_REQUEST = json_decode(stripslashes($_REQUEST));
}
6个回答

14
PHP字符串
'O\'Rei"lly'

这只是PHP获取字面值的方式。

O'Rei"lly

将其转换为可用的字符串。对该字符串调用addslashes会将其更改为以下11个字符的字面意义。

O\'Rei\"lly

strlen(addslashes('O\'Rei"lly')) == 11

这是发送到 json_escape 的值。

在JSON中,反斜杠是转义字符,需要进行转义,即

\ 转为 \\

同时单引号和双引号也可能会导致问题。将它们转换为Unicode等价形式是避免问题的一种方式。因此,PHP的json_encode的较新版本会更改:

' 转为 \u0027

" 转为 \u0022

因此,将这三个规则应用于

O\'Rei\"lly

提供给我们

O\\\u0027Rei\\\u0022lly

然后将此字符串用双引号括起来,以使其成为JSON字符串。您的替换表达式包括前导斜杠。无论是出于意外还是故意,这意味着json_encode返回的前导和尾随双引号不受转义,但它本不应该这样。

所以在早期版本的PHP中

$s = addslashes('O\'Rei"lly');
print json_encode($s);

会打印

"O\\'Rei\\\"lly"

我们希望将'改为\u0027,并将\"改为\u0022,因为\\"中只是为了将双引号"放入字符串中,因为它以双引号开头和结尾。

这就是为什么我们得到:

"O\\\u0027Rei\\\u0022lly"

1
有道理。因此,执行 str_replace(array('\"', '\''), array('\\u0022', '\\u0027'), json_encode(addslashes('O\'Rei"lly'))) 将始终产生与 json_encode(addslashes('O\'Rei"lly'), JSON_HEX_APOS | JSON_HEX_QUOT) 相同的输出,对吗? - Alix Axel

2

如果我理解正确,您只是想知道为什么需要使用

'\\\u0027'而不仅仅是'\\u0027'

您正在转义斜杠和字符unicode值。通过这样做,您告诉json应该在那里放置一个撇号,但它需要反斜杠和u来知道下一个是Unicode十六进制字符代码。

因为您正在转义此字符串:

$s = addslashes('O\'Rei"lly'); // O\'Rei\"lly

第一个反斜杠实际上是用来转义撇号前面的反斜杠。下一个斜杠用于转义json使用的反斜杠,以将该字符识别为unicode字符。

如果您将算法应用于O'Reilly而不是O\'Rei\"lly,则后者就足够了。

我希望这对您有所帮助。我只留下了这个链接供您阅读有关json构造的更多信息,因为显然您已经了解PHP:

http://www.json.org/fatfree.html


2
当您对字符串进行json编码时,一些内容必须进行转义,而不考虑选项。正如其他人所指出的那样,这包括'\',因此通过json_encode运行的任何反斜杠都将被加倍。由于您首先要通过addslashes运行字符串,该函数还会向引号添加反斜杠,因此您正在添加大量额外的反斜杠。以下功能将模拟json_encode如何对字符串进行编码。如果字符串已经添加了反斜杠,则它们将被加倍。
function json_encode_string( $encode , $options ) {
    $escape = '\\\0..\37';
    $needle = array();
    $replace = array();

    if ( $options & JSON_HEX_APOS ) {
        $needle[] = "'";
        $replace[] = '\u0027';
    } else {
        $escape .= "'";
    }

    if ( $options & JSON_HEX_QUOT ) {
        $needle[] = '"';
        $replace[] = '\u0022';
    } else {
        $escape .= '"';
    }

    if ( $options & JSON_HEX_AMP ) {
        $needle[] = '&';
        $replace[] = '\u0026';
    }

    if ( $options & JSON_HEX_TAG ) {
        $needle[] = '<';
        $needle[] = '>';
        $replace[] = '\u003C';
        $replace[] = '\u003E';
    }

    $encode = addcslashes( $encode , $escape );
    $encode = str_replace( $needle , $replace , $encode );

    return $encode;
}

2

它不仅转义了反斜杠,还转义了引号。处理转义的转义很困难,就像你在这里所做的一样,因为它很快就会变成反斜杠计数游戏。 :-/


1
@staticsan:\\\u0027 版本不应该只输出两个反斜杠并转义“u”吗? - Alix Axel
1
我认为'\\\u0027''\\\\u0027'做的事情是一样的,因为对于PHP来说,'\u'(注意单引号)没有任何意义,所以语义上是相同的。 - staticsan
我遇到了一个类似的问题,涉及到 PHP 中的引用和 JSON,这与 magic_quotes_gpc 有关,你检查一下魔术引号是否关闭了吗? - dabito
@dabito:不,magic_quotes是开启状态。str_replace存在是为了修复magic_quotes,请参见http://www.php.net/manual/en/function.get-magic-quotes-gpc.php#95697。 - Alix Axel
哎呀,正确的做法是关闭 magic_quotes。如果你不能这样做,那么你对任何输入的第一件事就是通过 stripslashes() 处理它。任何晚一点都太晚了,你会遇到你正在经历的混乱。 - staticsan
显示剩余6条评论

1

由于您将要对字符串\'进行json_encode,因此您需要先对\进行编码,然后再对'进行编码。所以您将得到\\\u0027。将这些连接起来的结果是\\\u0027


我仍然不明白。为什么\u0027需要转义?json_encode('"', JSON_HEX_QUOT); // "\u0022"json_encode("'", JSON_HEX_APOS) // "\u0027"返回类似的输出,但第一个不需要任何额外的斜杠。 - Alix Axel
你的原始字符串是 'O'Rei"lly'(全部在单引号中)。在单引号中,\ 不是转义字符。因此,在这种情况下,它也将被编码。如果你写成 "O'Rei"lly",你将得到所需的结果。 - Zsolti
在单引号中 "" 是转义字符。不过,我尝试了您的建议,但仍无法使用 \\u0027 - Alix Axel

0

addslashes()生成的\会被json_encode()重新转义。你可能想要说的是执行json_encode($s, JSON_HEX_APOS | JSON_HEX_QUOT)会输出以下内容,但你使用了$str而不是$s,这让大家感到困惑。

如果你在JavaScript中评估字符串"O\\\u0027Rei\\\u0022lly",你会得到"O\'rei\"lly",我非常确定这不是你想要的。当你评估它时,你可能需要删除所有控制代码。试试把这个放进文件里:alert("O\\\u0027Rei\\\u0022lly")

结论:你对引号进行了两次转义,这很可能不是你想要的。json_encode已经转义了所有必要的内容,以便任何JavaScript解析器都可以返回原始数据结构。在你的情况下,那就是在调用addslashes后获得的字符串。


证明:

<?php $out = json_encode(array(10, "h'ello", addslashes("h'ello re-escaped"))); ?>
<script type="text/javascript">
  var out = <?php echo $out; ?>;
  alert(out[0]);
  alert(out[1]);
  alert(out[2]);
</script>

我需要对字符串进行两次转义,因为我已经获得了带有斜杠的字符串,以防万一magic_quotes开启。但是你的回答并没有解决我替换转义字符的问题。 - Alix Axel
1
你没有仔细阅读。你不需要这样做。事实上,你 不应该 这样做。这只是因为addslashes()的缘故。\json_encode转义,因为它们被认为是你想要输出的字符串的一部分。你应该做的是禁用magic quotes(或强制执行array_walk_recursive($_REQUEST, 'stripslashes')),这样一切都会变得清晰明了。 - Tom
1
世界没有核武器会更安全,但这并不意味着没有任何核武器存在。我的原始字符串带有斜杠,这个问题的根本原因是为了避免在 PHP >= 5.2 < 5.3 中递归调用来修复 magic_quotes(请参见我在 http://www.php.net/manual/en/function.get-magic-quotes-gpc.php#95697 上提供的 PHP 5.3 解决方案)。 - Alix Axel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接