http_build_query之Java实现

23-09-02 17:11 字数 3499 阅读 830

同事Javaer对接三方支付渠道,对方只有PHP demo,签名始终不过,遂帮忙排查问题,定位到http_build_query,这个函数容易让Javaer误解,比较像是遍历拼接然后urlencode,所以就掉坑里了。先看下函数的定义

function http_build_query(     
    array|object $data,     
    string $numeric_prefix = "",     
    ?string $arg_separator = null,     
    int $encoding_type = PHP_QUERY_RFC1738 
): string

demo中只传了第一个参数,这里主要看下第四个参数默认值的含义: By default, PHP_QUERY_RFC1738. If enc_type is PHP_QUERY_RFC1738, then encoding is performed per » RFC 1738 and the application/x-www-form-urlencoded media type, which implies that spaces are encoded as plus (+) signs. If enc_type is PHP_QUERY_RFC3986, then encoding is performed according to » RFC 3986, and spaces will be percent encoded (%20).

RFC1738 有这么一段: Many URL schemes reserve certain characters for a special meaning: their appearance in the scheme-specific part of the URL has a designated semantics. If the character corresponding to an octet is reserved in a scheme, the octet must be encoded. The characters ";", "/", "?", ":", "@", "=" and "&" are the characters which may be reserved for special meaning within a scheme. No other characters may be reserved within a scheme.

没读懂,某些特殊字符会保留不转义?看来要追一下函数的源码(todo),上代码比较一下两种方式得到字符串的区别

$arr = [
    'title' => '充值',
    'userid' => '18',
    'notify_url' => 'https://www.baidu.com',
    'return_url' => 'https://www.baidu.com',
    'amount' => '1',
    'orderno' => '1626-116836307379@183#2064'
];
ksort($arr);
$str = '';
foreach ($arr as $k => $v) {
    $str .= $k . '=' . $v . '&';
}
$str = rtrim($str, '&');
echo 'http_build_query: ' . http_build_query($arr) . PHP_EOL;
echo 'urlencode: ' . urlencode($str) . PHP_EOL;

// 输出
http_build_query: amount=1&notify_url=https%3A%2F%2Fwww.baidu.com&orderno=1626-116836307379%40183%232064&return_url=https%3A%2F%2Fwww.baidu.com&title=%E5%85%85%E5%80%BC&userid=18
urlencode: amount%3D1%26notify_url%3Dhttps%3A%2F%2Fwww.baidu.com%26orderno%3D1626-116836307379%40183%232064%26return_url%3Dhttps%3A%2F%2Fwww.baidu.com%26title%3D%E5%85%85%E5%80%BC%26userid%3D18

可以看到http_build_query对字符串中的=和&未转义,既然如此替换一下urlencode结果中被转义的字符

echo str_replace(['%3D', '%26'], ['=', '&'], urlencode($str)) . PHP_EOL;

// 输出
amount=1&notify_url=https%3A%2F%2Fwww.baidu.com&orderno=1626-116836307379%40183%232064&return_url=https%3A%2F%2Fwww.baidu.com&title=%E5%85%85%E5%80%BC&userid=18

ok,按这个方式Java实现

public static void main(String[] args) {
    TreeMap<String, String> map = new TreeMap<>();
    map.put("title", "充值");
    map.put("userid", "18");
    map.put("notify_url", "https://www.baidu.com");
    map.put("return_url", "https://www.baidu.com");
    map.put("amount", "1");
    map.put("orderno", "1626-1168363073791832064");
    System.out.println(httpBuildQuery(map));
}

public static String httpBuildQuery(Map<String, String> map) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<String, String> entry : map.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        sb.append(key).append("=").append(value).append("&");
    }
    sb = new StringBuilder(sb.substring(0, sb.length() - 1));
    sb = new StringBuilder(URLEncoder.encode(sb.toString(), StandardCharsets.UTF_8));
    sb = new StringBuilder(sb.toString().replace("%3D", "=").replace("%26", "&"));
    return sb.toString();
}

问题是解决了,但是很明显这样的方式很片面,因为无法确定http_build_query还有哪些特殊字符是不转义的,想到两个解决办法

  1. 看http_build_query的源码
  2. 上面 RFC 1738 提到特殊字符都试一遍
1人点赞>
关注 收藏 改进 举报
0 条评论
排序方式 时间 投票
快来抢占一楼吧
请登录后发表评论