@@ -143,9 +143,12 @@ class Client
143143 */
144144 protected $ verifyhost = 2 ;
145145 /**
146- * @var int
146+ * @var int Corresponds to CURL_SSLVERSION_DEFAULT. Other CURL_SSLVERSION_ values are supported when in curl mode,
147+ * and in socket mode different values from 0 to 7, matching the corresponding curl value. Old php versions
148+ * do not support all values, php 5.4 and 5.5 do not support any in fact.
149+ * NB: please do not use any version lower than TLS 1.3 (value: 7) as they are considered insecure.
147150 */
148- protected $ sslversion = 0 ; // corresponds to CURL_SSLVERSION_DEFAULT. Other CURL_SSLVERSION_ values are supported
151+ protected $ sslversion = 0 ;
149152 /**
150153 * @var string
151154 */
@@ -582,9 +585,12 @@ public function setSSLVerifyHost($i)
582585 }
583586
584587 /**
585- * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let cURL decide
588+ * Set attributes for SSL communication: SSL version to use. Best left at 0 (default value): let PHP decide.
586589 *
587- * @param int $i see CURL_SSLVERSION_ constants
590+ * @param int $i use CURL_SSLVERSION_ constants. When in socket mode, use the same values: 2 (SSLv2) to 7 (TLSv1.3),
591+ * 0 for auto (note that old php versions do not support all TLS versions).
592+ * Note that, in curl mode, the actual ssl version in use might be higher than requested.
593+ * NB: please do not use any version lower than TLS 1.3 as they are considered insecure.
588594 * @return $this
589595 * @deprecated use setOption
590596 */
@@ -713,6 +719,7 @@ public function setCurlOptions($options)
713719
714720 /**
715721 * @param int $useCurlMode self::USE_CURL_ALWAYS, self::USE_CURL_AUTO or self::USE_CURL_NEVER
722+ * In 'auto' mode, curl is picked up based on features used, such as fe. NTLM auth, or https
716723 * @return $this
717724 * @deprecated use setOption
718725 */
@@ -724,7 +731,6 @@ public function setUseCurl($useCurlMode)
724731 return $ this ;
725732 }
726733
727-
728734 /**
729735 * Set user-agent string that will be used by this client instance in http headers sent to the server.
730736 *
@@ -745,7 +751,8 @@ public function setUserAgent($agentString)
745751 /**
746752 * @param null|int $component allowed values: PHP_URL_SCHEME, PHP_URL_HOST, PHP_URL_PORT, PHP_URL_PATH
747753 * @return string|int Notes: the path component will include query string and fragment; NULL is a valid value for port
748- * (in which case the default port for http/https will be used);
754+ * (in which case the default port for http/https will be used); the url scheme component will
755+ * reflect the `$method` used in the constructor, so it might not be http or https
749756 * @throws ValueErrorException on unsupported component
750757 */
751758 public function getUrl ($ component = null )
@@ -796,7 +803,10 @@ public function getUrl($component = null)
796803 * will be used. If that is 0, a platform specific timeout will apply.
797804 * This timeout value is passed to fsockopen(). It is also used for detecting server
798805 * timeouts during communication (i.e. if the server does not send anything to the client
799- * for $timeout seconds, the connection will be closed).
806+ * for $timeout seconds, the connection will be closed). When in CURL mode, this is the
807+ * CURL timeout.
808+ * NB: in both CURL and Socket modes, some conditions might lead to the client not
809+ * respecting the given timeout. Eg. if the network is not connected
800810 * @param string $method deprecated. Use the same value in the constructor instead.
801811 * Valid values are 'http', 'http11', 'https', 'h2' and 'h2c'. If left empty,
802812 * the http protocol chosen during creation of the object will be used.
@@ -839,12 +849,17 @@ public function send($req, $timeout = 0, $method = '')
839849 // where req is a Request
840850 $ req ->setDebug ($ this ->debug );
841851
842- /// @todo we could be smarter about this and not force usage of curl for https if not present as well as use the
843- /// presence of curl_extra_opts or socket_extra_opts as a hint
852+ /// @todo we could be smarter about this:
853+ /// - not force usage of curl if it is not present
854+ /// - not force usage of curl for https (minor BC)
855+ /// - use the presence of curl_extra_opts or socket_extra_opts as a hint
844856 $ useCurl = ($ this ->use_curl == self ::USE_CURL_ALWAYS ) || ($ this ->use_curl == self ::USE_CURL_AUTO && (
845857 in_array ($ method , array ('https ' , 'http11 ' , 'h2c ' , 'h2 ' )) ||
846858 ($ this ->username != '' && $ this ->authtype != 1 ) ||
847859 ($ this ->proxy != '' && $ this ->proxy_user != '' && $ this ->proxy_authtype != 1 )
860+ // uncomment the following if not forcing curl always for 'https'
861+ //|| ($this->sslversion == 7 && PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION == '7.3')
862+ //|| ($this->sslversion != 0 && PHP_MAJOR_VERSION < 6)
848863 ));
849864
850865 // BC - we go through sendPayloadCURL/sendPayloadSocket in case some subclass reimplemented those
@@ -978,20 +993,32 @@ protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
978993 $ connectServer = $ opts ['proxy ' ];
979994 $ connectPort = $ opts ['proxyport ' ];
980995 $ transport = 'tcp ' ;
981- /// @todo check: should we not use https in some cases?
982- $ uri = 'http:// ' . $ server . ': ' . $ port . $ path ;
996+ $ protocol = $ method ;
997+ if ($ method === 'http10 ' || $ method === 'http11 ' ) {
998+ $ protocol = 'http ' ;
999+ } elseif ($ method === 'h2 ' ) {
1000+ $ protocol = 'https ' ;
1001+ } else if (strpos ($ protocol , ': ' ) !== false ) {
1002+ $ this ->getLogger ()->error ('XML-RPC: ' . __METHOD__ . ": warning - attempted hacking attempt?. The protocol requested for the call is: ' $ protocol' " );
1003+ return new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['unsupported_option ' ], PhpXmlRpc::$ xmlrpcerr ['unsupported_option ' ] .
1004+ " attempted hacking attempt?. The protocol requested for the call is: ' $ protocol' " );
1005+ }
1006+ /// @todo this does not work atm (tested at least with an http proxy forwarding to an https server) - we
1007+ /// should implement the CONNECT protocol
1008+ $ uri = $ protocol . ':// ' . $ server . ': ' . $ port . $ path ;
9831009 if ($ opts ['proxy_user ' ] != '' ) {
9841010 if ($ opts ['proxy_authtype ' ] != 1 ) {
9851011 $ this ->getLogger ()->error ('XML-RPC: ' . __METHOD__ . ': warning. Only Basic auth to proxy is supported with HTTP 1.0 ' );
9861012 return new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['unsupported_option ' ],
987- PhpXmlRpc::$ xmlrpcerr ['unsupported_option ' ] . ': only Basic auth to proxy is supported with HTTP 1.0 ' );
1013+ PhpXmlRpc::$ xmlrpcerr ['unsupported_option ' ] . ': only Basic auth to proxy is supported with socket transport ' );
9881014 }
9891015 $ proxyCredentials = 'Proxy-Authorization: Basic ' . base64_encode ($ opts ['proxy_user ' ] . ': ' .
9901016 $ opts ['proxy_pass ' ]) . "\r\n" ;
9911017 }
9921018 } else {
9931019 $ connectServer = $ server ;
9941020 $ connectPort = $ port ;
1021+ /// @todo should we add support for 'h2' method? If so, is it 'tls' or 'tcp' ?
9951022 $ transport = ($ method === 'https ' ) ? 'tls ' : 'tcp ' ;
9961023 $ uri = $ path ;
9971024 }
@@ -1014,7 +1041,8 @@ protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
10141041 }
10151042
10161043 // omit port if default
1017- if (($ port == 80 && in_array ($ method , array ('http ' , 'http10 ' ))) || ($ port == 443 && $ method == 'https ' )) {
1044+ /// @todo add handling of http2, h2c in case they start being supported by fosckopen
1045+ if (($ port == 80 && in_array ($ method , array ('http ' , 'http10 ' , 'http11 ' ))) || ($ port == 443 && $ method == 'https ' )) {
10181046 $ port = '' ;
10191047 } else {
10201048 $ port = ': ' . $ port ;
@@ -1059,27 +1087,35 @@ protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
10591087 $ contextOptions ['ssl ' ]['verify_peer_name ' ] = $ opts ['verifypeer ' ];
10601088
10611089 if ($ opts ['sslversion ' ] != 0 ) {
1062- /// @see https://www.php.net/manual/en/function.curl-setopt.php, https://www.php.net/manual/en/migration56.openssl.php
1090+ /// @see https://www.php.net/manual/en/curl.constants.php,
1091+ /// https://www.php.net/manual/en/function.stream-socket-enable-crypto.php
1092+ /// https://www.php.net/manual/en/migration56.openssl.php,
1093+ /// https://wiki.php.net/rfc/improved-tls-constants
10631094 switch ($ opts ['sslversion ' ]) {
1064- /// @todo what does this map to? 1.0-1.3?
1065- //case 1: // TLSv1
1066- // break;
1095+ case 1 : // TLSv1x
1096+ if (version_compare (PHP_VERSION , '7.2.0 ' , '>= ' )) {
1097+ $ contextOptions ['ssl ' ]['crypto_method ' ] = STREAM_CRYPTO_METHOD_TLS_CLIENT ;
1098+ } else {
1099+ return new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['unsupported_option ' ],
1100+ PhpXmlRpc::$ xmlrpcerr ['unsupported_option ' ] . ': TLS-any only is supported with PHP 7.2 or later ' );
1101+ }
1102+ break ;
10671103 case 2 : // SSLv2
10681104 $ contextOptions ['ssl ' ]['crypto_method ' ] = STREAM_CRYPTO_METHOD_SSLv2_CLIENT;
10691105 break ;
10701106 case 3 : // SSLv3
10711107 $ contextOptions ['ssl ' ]['crypto_method ' ] = STREAM_CRYPTO_METHOD_SSLv3_CLIENT;
10721108 break ;
1073- case 4 : // TLSv1.0
1109+ case 4 : // TLSv1.0 - not always available?
10741110 $ contextOptions ['ssl ' ]['crypto_method ' ] = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT;
10751111 break ;
1076- case 5 : // TLSv1.1
1112+ case 5 : // TLSv1.1 - not always available?
10771113 $ contextOptions ['ssl ' ]['crypto_method ' ] = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
10781114 break ;
1079- case 6 : // TLSv1.2
1115+ case 6 : // TLSv1.2 - not always available?
10801116 $ contextOptions ['ssl ' ]['crypto_method ' ] = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
10811117 break ;
1082- case 7 : // TLSv1.3
1118+ case 7 : // TLSv1.3 - not always available
10831119 if (defined ('STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT ' )) {
10841120 $ contextOptions ['ssl ' ]['crypto_method ' ] = STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT;
10851121 } else {
@@ -1094,7 +1130,7 @@ protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
10941130 }
10951131 }
10961132
1097- foreach ($ opts ['extracurlopts ' ] as $ proto => $ protoOpts ) {
1133+ foreach ($ opts ['extrasockopts ' ] as $ proto => $ protoOpts ) {
10981134 foreach ($ protoOpts as $ key => $ val ) {
10991135 $ contextOptions [$ proto ][$ key ] = $ val ;
11001136 }
@@ -1111,6 +1147,13 @@ protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
11111147 $ this ->errno = 0 ;
11121148 $ this ->errstr = '' ;
11131149
1150+ /// @todo using `error_get_last` does not give us very detailed messages for connections errors, eg. for ssl
1151+ /// problems on php 5.6 we get 'Connect error: stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) (0)',
1152+ /// versus the more detailed warnings 'PHP Warning: stream_socket_client(): SSL operation failed with code 1. OpenSSL Error messages:
1153+ /// error:0A0C0103:SSL routines::internal error in /home/docker/workspace/src/Client.php on line 1121
1154+ /// PHP Warning: stream_socket_client(): Failed to enable crypto in /home/docker/workspace/src/Client.php on line 1121
1155+ /// PHP Warning: stream_socket_client(): unable to connect to tls://localhost:443 (Unknown error) in /home/docker/workspace/src/Client.php on line 1121'
1156+ /// This could be obviated by removing the `@` and capturing warnings via ob_start and co
11141157 $ fp = @stream_socket_client ("$ transport:// $ connectServer: $ connectPort " , $ this ->errno , $ this ->errstr , $ connectTimeout ,
11151158 STREAM_CLIENT_CONNECT , $ context );
11161159 if ($ fp ) {
@@ -1124,24 +1167,40 @@ protected function sendViaSocket($req, $method, $server, $port, $path, $opts)
11241167 }
11251168
11261169 $ this ->errstr = 'Connect error: ' . $ this ->errstr ;
1127- $ r = new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['http_error ' ], $ this ->errstr . ' ( ' . $ this ->errno . ') ' );
1128-
1129- return $ r ;
1170+ return new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['http_error ' ], $ this ->errstr . ' ( ' . $ this ->errno . ') ' );
11301171 }
11311172
1173+ /// @todo from here onwards, we can inject the results of stream_get_meta_data in the response. We could
1174+ /// do that f.e. only in new debug level 3, or starting at v1
1175+
11321176 if (!fputs ($ fp , $ op , strlen ($ op ))) {
11331177 fclose ($ fp );
11341178 $ this ->errstr = 'Write error ' ;
11351179 return new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['http_error ' ], $ this ->errstr );
11361180 }
11371181
1182+ $ info = stream_get_meta_data ($ fp );
1183+ if ($ info ['timed_out ' ]) {
1184+ fclose ($ fp );
1185+ $ this ->errstr = 'Write timeout ' ;
1186+ return new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['http_error ' ], $ this ->errstr );
1187+ }
1188+
11381189 // Close socket before parsing.
11391190 // It should yield slightly better execution times, and make easier recursive calls (e.g. to follow http redirects)
11401191 $ ipd = '' ;
11411192 do {
11421193 // shall we check for $data === FALSE?
11431194 // as per the manual, it signals an error
11441195 $ ipd .= fread ($ fp , 32768 );
1196+
1197+ $ info = stream_get_meta_data ($ fp );
1198+ if ($ info ['timed_out ' ]) {
1199+ fclose ($ fp );
1200+ $ this ->errstr = 'Read timeout ' ;
1201+ return new static::$ responseClass (0 , PhpXmlRpc::$ xmlrpcerr ['http_error ' ], $ this ->errstr );
1202+ }
1203+
11451204 } while (!feof ($ fp ));
11461205 fclose ($ fp );
11471206
@@ -1364,11 +1423,13 @@ protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
13641423 $ headers [] = 'Expect: ' ;
13651424
13661425 curl_setopt ($ curl , CURLOPT_HTTPHEADER , $ headers );
1367- // timeout is borked
1426+ // previous note: "timeout is borked" (on some old php/curl versions? It seems to work on 8.1. Maybe the issue
1427+ // has to do with dns resolution...)
13681428 if ($ opts ['timeout ' ]) {
1369- curl_setopt ($ curl , CURLOPT_TIMEOUT , $ opts ['timeout ' ] == 1 ? 1 : $ opts [ ' timeout ' ] - 1 );
1429+ curl_setopt ($ curl , CURLOPT_TIMEOUT , $ opts ['timeout ' ]);
13701430 }
13711431
1432+ // nb: for 'https' we leave it up to curl to decide
13721433 switch ($ method ) {
13731434 case 'http10 ' :
13741435 curl_setopt ($ curl , CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_0 );
@@ -1495,7 +1556,7 @@ protected function createCURLHandle($req, $method, $server, $port, $path, $opts)
14951556 * docs for the send() method". Please use setOption instead to set a timeout
14961557 * @param string $method deprecated. Was: "the http protocol variant to be used. See the details in the docs for the send() method."
14971558 * Please use the constructor to set an http protocol variant.
1498- * @param boolean $fallback deprecated. Was: "w"hen true, upon receiving an error during multicall, multiple single
1559+ * @param boolean $fallback deprecated. Was: "when true, upon receiving an error during multicall, multiple single
14991560 * calls will be attempted"
15001561 * @return Response[]
15011562 */
@@ -1712,6 +1773,8 @@ private function _try_multicall($reqs, $timeout, $method)
17121773 // *** BC layer ***
17131774
17141775 /**
1776+ * NB: always goes via socket, never curl
1777+ *
17151778 * @deprecated
17161779 *
17171780 * @param Request $req
@@ -1740,6 +1803,8 @@ protected function sendPayloadHTTP10($req, $server, $port, $timeout = 0, $userna
17401803 }
17411804
17421805 /**
1806+ * NB: always goes via curl, never socket
1807+ *
17431808 * @deprecated
17441809 *
17451810 * @param Request $req
@@ -1798,7 +1863,7 @@ protected function sendPayloadHTTPS($req, $server, $port, $timeout = 0, $usernam
17981863 * @param string $method 'http' (synonym for 'http10'), 'http10' or 'https'
17991864 * @param string $key
18001865 * @param string $keyPass @todo not implemented yet.
1801- * @param int $sslVersion @todo not implemented yet. See http://php.net/manual/en/migration56.openssl.php
1866+ * @param int $sslVersion
18021867 * @return Response
18031868 */
18041869 protected function sendPayloadSocket ($ req , $ server , $ port , $ timeout = 0 , $ username = '' , $ password = '' ,
0 commit comments