| 1128 | * HTTP request method uses PHP5 sockets to retrieve the url. |
| 1129 | * |
| 1130 | * Actually, this works pretty well. It supports SSL, nonblocking, quick, handles redirection and, |
| 1131 | * if cURL is not available and on PHP5, this will be preferred. |
| 1132 | * |
| 1133 | * @package WordPress |
| 1134 | * @subpackage HTTP |
| 1135 | * @since {unknown} |
| 1136 | */ |
| 1137 | class WP_Http_Sockets { |
| 1138 | /** |
| 1139 | * Send a HTTP request to a URI using streams with stream_socket_client(). |
| 1140 | * |
| 1141 | * @access public |
| 1142 | * @since {unknown} |
| 1143 | * |
| 1144 | * @param string $url |
| 1145 | * @param str|array $args Optional. Override the defaults. |
| 1146 | * @return array 'headers', 'body', 'cookies' and 'response' keys. |
| 1147 | */ |
| 1148 | function request($url, $args = array()) { |
| 1149 | $defaults = array( |
| 1150 | 'method' => 'GET', 'timeout' => 5, |
| 1151 | 'redirection' => 5, 'httpversion' => '1.0', |
| 1152 | 'blocking' => true, |
| 1153 | 'headers' => array(), 'body' => null, 'cookies' => array() |
| 1154 | ); |
| 1155 | |
| 1156 | $r = wp_parse_args( $args, $defaults ); |
| 1157 | |
| 1158 | // Construct Cookie: header if any cookies are set |
| 1159 | WP_Http::buildCookieHeader( $r ); |
| 1160 | |
| 1161 | if ( isset($r['headers']['User-Agent']) ) { |
| 1162 | $r['user-agent'] = $r['headers']['User-Agent']; |
| 1163 | unset($r['headers']['User-Agent']); |
| 1164 | } else if( isset($r['headers']['user-agent']) ) { |
| 1165 | $r['user-agent'] = $r['headers']['user-agent']; |
| 1166 | unset($r['headers']['user-agent']); |
| 1167 | } |
| 1168 | |
| 1169 | $arrURL = parse_url($url); |
| 1170 | |
| 1171 | // Not the best check for URLs, but works for the purposes of what we are using it. |
| 1172 | if ( false === $arrURL ) |
| 1173 | return new WP_Error('http_request_failed', sprintf(__('Malformed URL: %s'), $url)); |
| 1174 | |
| 1175 | // Only do this check once. |
| 1176 | if ( ! isset($r['ssl']) ) |
| 1177 | $r['ssl'] = $arrURL['scheme'] == 'https' || $arrURL['scheme'] == 'ssl'; |
| 1178 | |
| 1179 | // Nonblocking only works for SSL PHP 5.2.11+, might not work on Windows. |
| 1180 | // http://bugs.php.net/48182 |
| 1181 | if( $r['ssl'] && ! $r['blocking'] && version_compare(PHP_VERSION, '5.2.11', '<') ) |
| 1182 | return new WP_Error('http_request_failed', __('Not possible to do nonblocking request with SSL.')); |
| 1183 | |
| 1184 | // Create socket context. You only have ssl and socket transports. Socket has few options, |
| 1185 | // none that matter for HTTP requests. SSL has quite a few and we set those later. |
| 1186 | $context = stream_context_create(); |
| 1187 | |
| 1188 | $host = "tcp://".$arrURL['host'].':'.( isset($arrURL['port']) ? $arrURL['port'] : '80' ); |
| 1189 | |
| 1190 | if ( $r['ssl'] && extension_loaded('openssl') && in_array( 'ssl', stream_get_transports() ) ) { |
| 1191 | $host = "ssl://".$arrURL['host'].':'.( isset($arrURL['port']) ? $arrURL['port'] : '443' ); |
| 1192 | |
| 1193 | $is_local = isset($r['local']) && $r['local']; |
| 1194 | $ssl_verify = isset($r['sslverify']) && $r['sslverify']; |
| 1195 | if ( $is_local ) |
| 1196 | $ssl_verify = apply_filters('https_local_ssl_verify', $ssl_verify); |
| 1197 | elseif ( ! $is_local ) |
| 1198 | $ssl_verify = apply_filters('https_ssl_verify', $ssl_verify); |
| 1199 | |
| 1200 | stream_context_set_option($context, 'ssl', 'verify_peer', $ssl_verify); |
| 1201 | stream_context_set_option($context, 'ssl', 'verify_host', $ssl_verify); |
| 1202 | } |
| 1203 | |
| 1204 | // This is the message body for the request, it includes the header and the body if available. |
| 1205 | // It works similar to Fsockopen, in that it doesn't handle HTTP protocol directly. |
| 1206 | $request_message = $this->_build_request_message($r, $url, $arrURL); |
| 1207 | |
| 1208 | // Holds the error number. |
| 1209 | $error_num = 0; |
| 1210 | |
| 1211 | // Holds the error string. |
| 1212 | $error_string = ''; |
| 1213 | |
| 1214 | $proxy = new WP_HTTP_Proxy(); |
| 1215 | |
| 1216 | $flags = STREAM_CLIENT_CONNECT; |
| 1217 | if ( ! $r['blocking'] ) |
| 1218 | $flags = STREAM_CLIENT_ASYNC_CONNECT | STREAM_CLIENT_CONNECT; |
| 1219 | |
| 1220 | // stream_socket_client only supports tcp and udp, change to tcp for HTTP protocol. |
| 1221 | if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) |
| 1222 | $handle = stream_socket_client( $proxy->host().':'.$proxy->port(), $error_num, $error_string, $r['timeout'], $flags, $context ); |
| 1223 | else |
| 1224 | $handle = stream_socket_client( $host, $error_num, $error_string, $r['timeout'], $flags, $context ); |
| 1225 | |
| 1226 | if ( ! $handle ) |
| 1227 | return new WP_Error('http_request_failed', sprintf(__('Could not open handle for stream_socket_client() to %s'), $url)); |
| 1228 | |
| 1229 | $timeout = (int) floor( $r['timeout'] ); |
| 1230 | $utimeout = $timeout == $r['timeout'] ? 0 : 1000000 * $r['timeout'] % 1000000; |
| 1231 | |
| 1232 | if ( ! $r['blocking'] ) { |
| 1233 | stream_set_blocking($handle, 0); |
| 1234 | $read = $except = null; |
| 1235 | $write = array($handle); |
| 1236 | while ( ! empty( $request_message ) ) { |
| 1237 | $nonblocking = stream_select($read, $write, $except, $timeout, $utimeout); |
| 1238 | |
| 1239 | if ( $nonblocking === false ) |
| 1240 | return new WP_Error('http_request_failed', sprintf(__('Could not handle nonblocking request to %s'), $url)); |
| 1241 | |
| 1242 | // Short requests should complete after the first try, but longer ones will only |
| 1243 | // send so much before kicking it back out. |
| 1244 | $bytes_written = fwrite( $handle, $request_message, strlen($request_message) ); |
| 1245 | |
| 1246 | // Dare god I hope this works. May mess up with UTF8 strings, but the execution after will catch it. |
| 1247 | if ( $bytes_written == strlen($request_message) ) |
| 1248 | break; |
| 1249 | |
| 1250 | $request_message = substr($request_message, $bytes_written); |
| 1251 | } |
| 1252 | |
| 1253 | fclose($handle); |
| 1254 | return array( 'headers' => array(), 'body' => '', 'response' => array('code' => false, 'message' => false), 'cookies' => array() ); |
| 1255 | } else { |
| 1256 | // We don't use stream_set_timeout with stream_select(); |
| 1257 | stream_set_timeout( $handle, $timeout, $utimeout ); |
| 1258 | } |
| 1259 | |
| 1260 | while ( ! empty( $request_message ) ) { |
| 1261 | // Short requests should complete after the first try, but longer ones will only send so |
| 1262 | // much before kicking it back out. |
| 1263 | $bytes_written = fwrite( $handle, $request_message, strlen($request_message) ); |
| 1264 | |
| 1265 | if ( $bytes_written == strlen($request_message) ) |
| 1266 | break; |
| 1267 | |
| 1268 | $request_message = substr($request_message, $bytes_written); |
| 1269 | } |
| 1270 | |
| 1271 | $response = stream_get_contents($handle); |
| 1272 | |
| 1273 | fclose($handle); |
| 1274 | |
| 1275 | $process = WP_Http::processResponse($response); |
| 1276 | $arrHeaders = WP_Http::processHeaders($process['headers']); |
| 1277 | |
| 1278 | // Is the response code within the 400 range? |
| 1279 | if ( (int) $arrHeaders['response']['code'] >= 400 && (int) $arrHeaders['response']['code'] < 500 ) |
| 1280 | return new WP_Error('http_request_failed', $arrHeaders['response']['code'] . ': ' . $arrHeaders['response']['message']); |
| 1281 | |
| 1282 | // If location is found, then assume redirect and redirect to location. |
| 1283 | if ( 'HEAD' != $r['method'] && isset($arrHeaders['headers']['location']) ) { |
| 1284 | if ( $r['redirection']-- > 0 ) { |
| 1285 | return $this->request($arrHeaders['headers']['location'], $r); |
| 1286 | } else { |
| 1287 | return new WP_Error('http_request_failed', __('Too many redirects.')); |
| 1288 | } |
| 1289 | } |
| 1290 | |
| 1291 | // If the body was chunk encoded, then decode it. |
| 1292 | if ( ! empty( $process['body'] ) && isset( $arrHeaders['headers']['transfer-encoding'] ) && 'chunked' == $arrHeaders['headers']['transfer-encoding'] ) |
| 1293 | $process['body'] = WP_Http::chunkTransferDecode($process['body']); |
| 1294 | |
| 1295 | if ( true === $r['decompress'] && true === WP_Http_Encoding::should_decode($arrHeaders['headers']) ) |
| 1296 | $process['body'] = WP_Http_Encoding::decompress( $process['body'] ); |
| 1297 | |
| 1298 | return array('headers' => $arrHeaders['headers'], 'body' => $process['body'], 'response' => $arrHeaders['response'], 'cookies' => $arrHeaders['cookies']); |
| 1299 | } |
| 1300 | |
| 1301 | /** |
| 1302 | * Builds the request message for sending to the URL. |
| 1303 | * |
| 1304 | * @access private |
| 1305 | * @since {unknown} |
| 1306 | * |
| 1307 | * @param array $r Request arguments. |
| 1308 | * @param string $url URL string. |
| 1309 | * @param array $arrURL Processed URL string. |
| 1310 | * @return string Full request message. |
| 1311 | */ |
| 1312 | function _build_request_message($r, $url, &$arrURL) { |
| 1313 | $proxy = new WP_HTTP_Proxy(); |
| 1314 | |
| 1315 | // Create request headers and combine body. |
| 1316 | if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) //Some proxies require full URL in this field. |
| 1317 | $requestPath = $url; |
| 1318 | else |
| 1319 | $requestPath = $arrURL['path'] . ( isset($arrURL['query']) ? '?' . $arrURL['query'] : '' ); |
| 1320 | |
| 1321 | if ( empty($requestPath) ) |
| 1322 | $requestPath .= '/'; |
| 1323 | |
| 1324 | $strHeaders = strtoupper($r['method']) . ' ' . $requestPath . ' HTTP/' . $r['httpversion'] . "\r\n"; |
| 1325 | |
| 1326 | if ( $proxy->is_enabled() && $proxy->send_through_proxy( $url ) ) |
| 1327 | $strHeaders .= 'Host: ' . $arrURL['host'] . ':' . $arrURL['port'] . "\r\n"; |
| 1328 | else |
| 1329 | $strHeaders .= 'Host: ' . $arrURL['host'] . "\r\n"; |
| 1330 | |
| 1331 | if ( isset($r['user-agent']) ) |
| 1332 | $strHeaders .= 'User-agent: ' . $r['user-agent'] . "\r\n"; |
| 1333 | |
| 1334 | if ( is_array($r['headers']) ) { |
| 1335 | foreach ( (array) $r['headers'] as $header => $headerValue ) |
| 1336 | $strHeaders .= $header . ': ' . $headerValue . "\r\n"; |
| 1337 | } else { |
| 1338 | $strHeaders .= $r['headers']; |
| 1339 | } |
| 1340 | |
| 1341 | if ( $proxy->use_authentication() ) |
| 1342 | $strHeaders .= $proxy->authentication_header() . "\r\n"; |
| 1343 | |
| 1344 | $strHeaders .= "\r\n"; |
| 1345 | |
| 1346 | if ( ! is_null($r['body']) ) |
| 1347 | $strHeaders .= $r['body']; |
| 1348 | |
| 1349 | return $strHeaders; |
| 1350 | } |
| 1351 | |
| 1352 | /** |
| 1353 | * Whether this class can be used for retrieving an URL. |
| 1354 | * |
| 1355 | * @static |
| 1356 | * @access public |
| 1357 | * @since {unknown} |
| 1358 | * |
| 1359 | * @return boolean False means this class can not be used, true means it can. |
| 1360 | */ |
| 1361 | function test($args = array()) { |
| 1362 | if ( ! function_exists('stream_socket_client') ) |
| 1363 | return false; |
| 1364 | |
| 1365 | return apply_filters('use_socket_transport', true, $args); |
| 1366 | } |
| 1367 | } |
| 1368 | |
| 1369 | /** |