http_build_query函数的urlencoding过多

时间:2014-01-10 14:20:22

标签: php url query-string urlencode

为什么在使用http_build_query函数构建查询字符串时,它会在值外部对方括号[]进行urlencodes以及如何摆脱它?

$query = array("var" => array("foo" => "value", "bar" => "encodedBracket["));
$queryString = http_build_query($query, "", "&");
var_dump($queryString);
var_dump("urldecoded: " . urldecode($queryString));

输出:

var%5Bfoo%5D=value&var%5Bbar%5D=encodedBracket%5B
urldecoded: var[foo]=value&var[bar]=encodedBracket[

该函数在输出的第一行中正确地对[中的encodedBracket[进行了编码,但在var[foo]=var[bar]=中对方括号进行编码的原因是什么?正如您所看到的,urldecoding字符串也解码了值中的保留字符,encodedBracket%5B应该保持原样,查询字符串是正确的,而不是encodedBracket[

根据section 2.2 Reserved Characters of Uniform Resource Identifier (URI): Generic Syntax

  

URI包括由分隔的组件和子组件   “保留”集中的字符。这些字符被调用   “保留”因为它们可能(或可能不)被定义为分隔符   通用语法,每种特定于方案的语法,或者由   URI的解除引用算法的特定于实现的语法。如果   URI组件的数据将与保留字符冲突   作为分隔符的目的,那么冲突的数据必须是   在URI形成之前进行百分比编码。

     

reserved = gen-delims / sub-delims

     

gen-delims =“:”/“/”/“?” /“#”/“[”/“]”/“@”

     

sub-delims =“!” /“$”/“&” /“'”/“(”/“)”/“*”/“+”/“,”/ /   “;” /“=”

所以不应该http_build_query真正产生更可读的输出,只有[]字符的urlencoded只在需要的地方?如何让它产生这样的输出?

4 个答案:

答案 0 :(得分:0)

我发现以下内容"修复" here

  

[...]可行的修复'我一直在使用is来后处理http_build_query()输出   以下 - 解决方案'这让我的皮肤爬了一点:

function http_build_query_unborker($s) {
    return preg_replace_callback('#%5[bd](?=[^&]*=)#i', function($match) {
        return urldecode($match[0]); 
    }, $s);
}

所以现在它会变成:

$query = array("var" => array("foo" => "value", "bar" => "encodedBracket["));
$queryString = http_build_query_unborker(http_build_query($query, "", "&"));
var_dump($queryString);
var_dump("urldecoded: " . urldecode($queryString)); // var[foo]=value&var[bar]=encodedBracket%5B

答案 1 :(得分:0)

你这里有很多问题。用RFC的术语表达,并用这些相同的术语阅读你自己的问题。我从下到上提出你的问题:

  

如何让它产生这样的输出?

使用其他编码器 Net_URL2 pear / packagist)例如:

$vars = array("var" => array("foo" => "value", "bar" => "encodedBracket["));

$url = new Net_URL2('');
$url->setQueryVariables($vars);
$query = $url->getQuery();

var_dump($query); // string(41) "var[foo]=value&var[bar]=encodedBracket%5B"
  

所以不应该http_build_query really使用[] urlencoded这样的字符产生更可读的输出吗?

不,不应该。即使不需要对查询部分内的方括号进行编码,也建议使用。应该做的就是建议。

除此之外,http_build_query()功能与创建"更易读的输出" 无关。它只是关于创建HTTP URI的查询。对于这样的查询部分,方括号应该是百分比编码的。这些是未特别允许查询的保留字符。

  

var[foo]=var[bar]=编码方括号的原因是什么?

编码方括号的原因与编码encodedBracket[中的方括号的原因相同。在你的问题中你在这些部分之间做的区别纯粹是单独的语法,在URI中这些部分被视为相等。 URI中没有查询部分的子部分。因此,对括号var[或括号encodedBracket[进行区分与查询部分的URI编码完全无关。

正如你所说encodedBracket[encodedBracket%5B的百分比编码是正确的,因为它属于URI的同一部分(查询部分),逻辑规定你必须接受该编码在var[var%5B中的括号在URI编码方面同样正确。相同的URI部分,相同的编码。查询部分唯一的结尾分隔符是" #"。

另外,你的推理表明这部分存在误解:

  

正如您所看到的,urldecoding字符串也解码了值中的保留字符,encodedBracket%5B应该保持原样,查询字符串是正确的,而不是encodedBracket[

如果你urldecode,所有百分比编码序列都将被解码 - 无论百分比编码是否代表保留字符。就正确而言,它与您所说的相反:%5B必须被解码为[,无论它是在字符串的开头,中间还是末尾

  

为什么在使用http_build_query函数构建查询字符串时,urlencodes方括号[]外部值以及如何摆脱它?

回答第二部分更容易,在答案开头看,已经回答了。

关于为什么这可能不会立即可见,特别是因为您可能已经发现PHP本身在查询中接受百分比编码和逐字方括号(甚至混合)而没有任何问题。

为什么会出现这种差异?它是否真的像你在问题中描述的那样简单?这只是一种美容差异吗?

首先,在查询部分不应包含来自gen-delims字符未加密码的括号中,不在URI的查询查询部分中编码方括号会违反RFC3986 。根据ABNF,非百分比编码的方括号不能成为查询的一部分:

 query         = *( pchar / "/" / "?" )

 pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"

 unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"

 pct-encoded   = "%" HEXDIG HEXDI

 sub-delims    = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="

因此不建议删除这些(至少为了符合标准的编码目的),因为它会改变URI:

  

用保留字符替换保留字符的URI      相应的百分比编码的八位字节不相等。

这已经是一个很好的暗示,对于你要求的URI,它与PHP通过内置函数创建的URI具有不同的含义。

进一步说:

  

生成URI的应用程序应对数据八位字节进行百分比编码      除非这些字符对应于保留集中的字符      URI方案特别允许表示其中的数据      零件。

对于gen-delims 中的所有字符而言,情况并非如此,但是对于ABNF而言

"/" / "?" / ":" / "@"

因此看起来http_build_query()路由到百分比编码方括号,因为那些是保留字符,特定于该部分的URI方案(查询) 。基本上没有任何问题,它遵循RFC3986的建议。并且它不会为查询的那些部分建议不同的含义

但是你清楚地说,从技术上讲,这些括号在查询中不是分隔符。是的,这是真的:

  

查询组件由第一个问题指示      标记("?")字符并以数字符号("#")字符结束      或者在URI的末尾。

因此,与之前确定的保留字符而不是特别允许的情况相比:

"#" / "[" / "]"

(已经是一个非常小的清单)应该清楚" #"必须保持保留,否则URI会被破坏(在查询结束时是一个真正的分隔分隔符),但是在表示不相等的URI而不保留数据并且保留所有URI分隔符时,不能特别允许使用方括号:

  

如果在URI组件中找到保留字符,则      对于该角色没有分隔角色,那么它必须是      解释为表示与其对应的数据八位字节      字符在US-ASCII中的编码。

因此,如果您仍然可以关注我,可能需要实际执行您要求的内容:创建一个URI,其中方括号表示分隔符(例如,表示数组定义的一小部分)但没有这是数据。尽管根据RFC 3986保留了该字符的数据。

因此,在技术上可以创建一个带有方括号的URI,而不是在查询中编码的百分比。从技术上讲,即使是内部值,就像它在值之外的语法差异一样,这只是值内部的另一个语法差异。

当您在浏览器中输入这些内容时,这也是浏览器保留方括号状态的原因。是否编码百分比 - 浏览器按原样将URI的那部分传递给服务器,以便服务器上的基础进程可以从可能由此表达的语法差异中受益。

因此,请为底层平台正确选择URL编码。只是因为它可能,它并不意味着它以稳定的方式运作。 http_build_query()的方式是遵循RFC 3986的最稳定(安全)方式。然而,它应该在RFC中,所以如果您理解这一点,那么您可以有正当理由不进行百分比编码方括号。

您在问题中提到的一个原因是可读性。当您在一张纸上写下URL时,这一点尤为重要。我不太确定方括号是否是一个如此好的可区分字符,如果不是百分比编码甚至有助于可读性。但我没试过。 PHP会接受这两种方式。但是你不需要以编程方式做到这一点。因此,在您的方案中可能无法实现可读性。

答案 2 :(得分:0)

这是我编写的快速函数,用于生成更好的查询字符串。它不仅不编码方括号,而且如果它匹配索引,也将省略数组键。 请注意,它不支持对象或http_build_query的附加选项。 $prefix参数用于递归,在初始调用时应省略。

function http_clean_query(array $query_data, string $prefix=null): string {
    $parts = [];
    $i = 0;
    foreach ($query_data as $key=>$value) {
        if ($prefix === null) {
            $key = rawurlencode($key);
        } else if ($key === $i) {
            $key = $prefix.'[]';
            $i++;
        } else {
            $key = $prefix.'['.rawurlencode($key).']';
        }
        if (is_array($value)) {
            if (!empty($value)) $parts[] = http_clean_query($value, $key);
        } else {
            $parts[] = $key.'='.rawurlencode($value);
        }
    }
    return implode('&', $parts);
}

答案 3 :(得分:0)

我知道这有点过时了,但我认为它今天仍然适用。

TL;DR:http_build_query() 工作正常

更长的解释:是的,> #from my layout.html > > <!doctype html> <html lang="en"> > <head> > <meta charset="utf-8"> > <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> > <link href="/static/favicon.ico" rel="icon" type="image/x-icon"/> > <title>{% block title %}Title{% endblock %}</title> > {% block firebase1 %}{% endblock %} > {% block firebase2 %}{% endblock %} > </head> http_build_query 进行编码,看起来很糟糕……但这是正确的行为:[]保留字符,根据 { {3}}。而且...不,它们不是为传递数组保留的

[] 所保留的内容在 rfc3986#section-2.3 中定义:

<块引用>

由 Internet 协议文字地址、版本标识的主机 6 [RFC3513] 或更高版本,通过包含 IP 文字来区分 方括号内(“[”和“]”)。 这是唯一的地方
URI 语法中允许使用方括号字符。在
对未来尚未定义的 IP 文字地址格式的预期,
一个实现可以使用一个可选的版本标志来指示这样一个 明确格式化,而不是依赖启发式确定。

[]

所以基本上它是为诸如 IP-literal = "[" ( IPv6address / IPvFuture ) "]" IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )

之类的东西保留的

这里还有一个关于同一主题的问题:rfc3986#section-3.2.2