Why does tlstest.paypal.com work from browser but

2019-02-21 06:08发布

问题:

After 2018 June 30th, Paypal won't accept non-TLS 1.2 + HTTP 1.1 requests anymore.
They created the URL https://tlstest.paypal.com/ to test if connections are OK. If we open this URL in a browser, we get a successful:

PayPal_Connection_OK

Quesiton: why does it fail when connecting from PHP with the following code? (I get no response at all, the browser is still in waiting "state" like this, so it doesn't even arrive at echo $errno; echo $errstr;)

<?php
$req = '';    // usually I use $req = 'cmd=_notify-validate'; for IPN
$header .= "POST / HTTP/1.1\r\n";
$header .= "Host: tlstest.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen('tls://tlstest.paypal.com', 443, $errno, $errstr, 30);

if (!$fp) {
    echo $errno;
    echo $errstr;
} else {
    fputs($fp, $header);
    while (!feof($fp))
    {
        $res = fgets($fp, 1024);
        echo $res;
    }
    fclose($fp);
}
?>

Note:

  • this is not a duplicate of Paypal IPN HTTP/1.1 - doesn't provide any response at all, it is an empty string, the latter is outdated.

  • I have PHP 5.6.33-0+deb8u1 (cli) (built: Jan 5 2018 15:46:26) and openssl version text: OpenSSL 1.0.1t 3 May 2016

回答1:

It works on my side by changing tls:// to ssl:// which makes absolutely no sense to me, but this is also why using fsockopen is a too low level library to just do HTTP exchanges with it (you should use a proper HTTP client library) and at the same time not configurable enough regarding TLS stuff.

With $fp = fsockopen('tls://tlstest.paypal.com', 443, $errno, $errstr, 30); I get :

HTTP/1.1 426 Unknown
Server: AkamaiGHost
Mime-Version: 1.0
Content-Type: text/html
Content-Length: 267
Expires: Fri, 22 Jun 2018 19:49:46 GMT
Date: Fri, 22 Jun 2018 19:49:46 GMT
Connection: keep-alive
Upgrade: TLS/1.2

<HTML><HEAD>
<TITLE>Access Denied</TITLE>
</HEAD><BODY>
<H1>Access Denied</H1>

You don't have permission to access "http&#58;&#47;&#47;tlstest&#46;paypal&#46;com&#47;" on this server.<P>
Reference&#32;&#35;18&#46;8024a17&#46;1529696986&#46;1fc51318
</BODY>
</HTML>

but with $fp = fsockopen('ssl://tlstest.paypal.com', 443, $errno, $errstr, 30); I get:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Date: Fri, 22 Jun 2018 20:05:35 GMT
Connection: keep-alive

And then it hangs, probably because it is a keep-alive connection and the buffer is smaller than 1024 so that you do not get the following body content. Which is probably "PayPal_Connection_OK", as it exactly matches the length displayed in Content-Length. This again shows that you should use an HTTP client library instead of trying to (badly) reimplement HTTP on top of fsockopen.



回答2:

For completeness, here is a working code (full credit to PatrickMevzek's answer):

<?php
$req = '';
$header = "POST / HTTP/1.1\r\n";
$header .= "Host: tlstest.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen('ssl://tlstest.paypal.com', 443, $errno, $errstr, 3);

if (!$fp) {
    echo $errno;
    echo $errstr;
} else {
    fputs($fp, $header);
    while (!feof($fp))
    {
        $res = fgets($fp, 21);   // 21 because length of "PayPal_Connection_OK"
        echo $res;
    }
    fclose($fp);
}
?>

Here is the answer from server:

# php -f test.php
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 20
Date: Fri, 22 Jun 2018 20:19:56 GMT
Connection: keep-alive

PayPal_Connection_OK