22 require_once
'HTTP/Request2/Adapter.php';
25 require_once
'HTTP/Request2/SocketWrapper.php';
45 const REGEXP_TOKEN =
'[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';
130 $this->socket->write($headers);
132 $this->request->setLastEvent(
'sentHeaders', $headers);
134 if (!$this->expect100Continue) {
140 if (!$response || 100 == $response->getStatus()) {
141 $this->expect100Continue =
false;
150 $jar->addCookiesFromResponse($response, $request->
getUrl());
163 if ($authInfo = $response->getHeader(
'authentication-info')) {
166 if ($proxyInfo = $response->getHeader(
'proxy-authentication-info')) {
170 }
catch (Exception $e) {
174 unset($this->request, $this->requestBody);
177 $this->redirectCountdown = null;
181 if (!$request->
getConfig(
'follow_redirects') || !$response->isRedirect()) {
182 $this->redirectCountdown = null;
197 $secure = 0 == strcasecmp($this->request->getUrl()->getScheme(),
'https');
199 $headers = $this->request->getHeaders();
200 $reqHost = $this->request->getUrl()->getHost();
201 if (!($reqPort = $this->request->getUrl()->getPort())) {
202 $reqPort = $secure? 443: 80;
205 $httpProxy = $socksProxy =
false;
206 if (!($host = $this->request->getConfig(
'proxy_host'))) {
210 if (!($port = $this->request->getConfig(
'proxy_port'))) {
212 'Proxy port not provided',
216 if (
'http' == ($type = $this->request->getConfig(
'proxy_type'))) {
218 } elseif (
'socks5' == $type) {
222 "Proxy type '{$type}' is not supported"
227 if ($tunnel && !$httpProxy) {
229 "Trying to perform CONNECT request without proxy",
233 if ($secure && !in_array(
'ssl', stream_get_transports())) {
235 'Need OpenSSL support for https:// requests',
242 if ($httpProxy && !$secure && !empty($headers[
'connection'])
243 &&
'Keep-Alive' == $headers[
'connection']
245 $this->request->setHeader(
'connection');
248 $keepAlive = (
'1.1' == $this->request->getConfig(
'protocol_version') &&
249 empty($headers[
'connection'])) ||
250 (!empty($headers[
'connection']) &&
251 'Keep-Alive' == $headers[
'connection']);
254 if ($ip = $this->request->getConfig(
'local_ip')) {
255 $options[
'socket'] = array(
256 'bindto' => (
false === strpos($ip,
':') ? $ip :
'[' . $ip .
']') .
':0'
259 if ($secure || $tunnel) {
260 $options[
'ssl'] = array();
261 foreach ($this->request->getConfig() as $name => $value) {
262 if (
'ssl_' == substr($name, 0, 4) && null !== $value) {
263 if (
'ssl_verify_host' == $name) {
265 $options[
'ssl'][
'CN_match'] = $reqHost;
268 $options[
'ssl'][substr($name, 4)] = $value;
272 ksort($options[
'ssl']);
276 if ($timeout = $this->request->getConfig(
'timeout')) {
277 $deadline = time() + $timeout;
284 $remote = ((!$secure || $httpProxy || $socksProxy)?
'tcp://':
'ssl://')
285 . $host .
':' . $port;
286 $socketKey = $remote . (
287 ($secure && $httpProxy || $socksProxy)
288 ?
"->{$reqHost}:{$reqPort}" :
''
289 ) . (empty($options)?
'':
':' . serialize($options));
290 unset($this->socket);
294 if ($keepAlive && !empty(self::$sockets[$socketKey])
295 && !self::$sockets[$socketKey]->eof()
297 $this->socket =& self::$sockets[$socketKey];
301 require_once
'HTTP/Request2/SOCKS5.php';
304 $remote, $this->request->getConfig(
'connect_timeout'),
305 $options, $this->request->getConfig(
'proxy_user'),
306 $this->request->getConfig(
'proxy_password')
309 $this->socket->
setDeadline($deadline, $this->request->getConfig(
'timeout'));
310 $this->socket->connect($reqHost, $reqPort);
312 $conninfo =
"tcp://{$reqHost}:{$reqPort} via {$remote}";
314 $this->socket->enableCrypto();
315 $conninfo =
"ssl://{$reqHost}:{$reqPort} via {$remote}";
318 } elseif ($secure && $httpProxy && !$tunnel) {
320 $conninfo =
"ssl://{$reqHost}:{$reqPort} via {$remote}";
324 $remote, $this->request->getConfig(
'connect_timeout'), $options
327 $this->request->setLastEvent(
'connect', empty($conninfo)? $remote: $conninfo);
330 $this->socket->setDeadline($deadline, $this->request->getConfig(
'timeout'));
349 array_merge($this->request->getConfig(), array(
'adapter' => $donor))
351 $response = $connect->send();
353 if (200 > $response->getStatus() || 300 <= $response->getStatus()) {
355 'Failed to connect via HTTPS proxy. Proxy response: ' .
356 $response->getStatus() .
' ' . $response->getReasonPhrase()
359 $this->socket = $donor->socket;
360 $this->socket->enableCrypto();
381 $lengthKnown =
'chunked' == strtolower($response->
getHeader(
'transfer-encoding'))
382 || null !== $response->
getHeader(
'content-length')
385 || in_array($response->
getStatus(), array(204, 304));
386 $persistent =
'keep-alive' == strtolower($response->
getHeader(
'connection')) ||
387 (null === $response->
getHeader(
'connection') &&
389 return $requestKeepAlive && $lengthKnown && $persistent;
397 if (!empty($this->socket)) {
398 $this->socket = null;
399 $this->request->setLastEvent(
'disconnect');
419 if (is_null($this->redirectCountdown)) {
420 $this->redirectCountdown = $request->
getConfig(
'max_redirects');
422 if (0 == $this->redirectCountdown) {
423 $this->redirectCountdown = null;
426 'Maximum (' . $request->
getConfig(
'max_redirects') .
') redirects followed',
435 if ($redirectUrl->isAbsolute()
436 && !in_array($redirectUrl->getScheme(), array(
'http',
'https'))
438 $this->redirectCountdown = null;
440 'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(),
446 if (!$redirectUrl->isAbsolute()) {
447 $redirectUrl = $request->
getUrl()->resolve($redirectUrl);
450 $redirect->setUrl($redirectUrl);
452 || (!$request->getConfig(
'strict_redirects')
453 && in_array($response->
getStatus(), array(301, 302)))
456 $redirect->setBody(
'');
459 if (0 < $this->redirectCountdown) {
460 $this->redirectCountdown--;
486 if (401 != $response->
getStatus() || !$this->request->getAuth()) {
493 $url = $this->request->getUrl();
494 $scheme = $url->getScheme();
495 $host = $scheme .
'://' . $url->getHost();
496 if ($port = $url->getPort()) {
497 if ((0 == strcasecmp($scheme,
'http') && 80 != $port)
498 || (0 == strcasecmp($scheme,
'https') && 443 != $port)
500 $host .=
':' . $port;
504 if (!empty($challenge[
'domain'])) {
506 foreach (preg_split(
'/\\s+/', $challenge[
'domain']) as $prefix) {
508 if (
'/' == substr($prefix, 0, 1)) {
509 $prefixes[] = $host . $prefix;
513 if (empty($prefixes)) {
514 $prefixes = array($host .
'/');
518 foreach ($prefixes as $prefix) {
519 if (!empty(self::$challenges[$prefix])
520 && (empty($challenge[
'stale']) || strcasecmp(
'true', $challenge[
'stale']))
525 self::$challenges[$prefix] =& $challenge;
550 if (407 != $response->
getStatus() || !$this->request->getConfig(
'proxy_user')) {
557 $key =
'proxy://' . $this->request->getConfig(
'proxy_host') .
558 ':' . $this->request->getConfig(
'proxy_port');
560 if (!empty(self::$challenges[$key])
561 && (empty($challenge[
'stale']) || strcasecmp(
'true', $challenge[
'stale']))
567 self::$challenges[$key] = $challenge;
602 $authParam =
'(' . self::REGEXP_TOKEN .
')\\s*=\\s*(' .
603 self::REGEXP_TOKEN .
'|' . self::REGEXP_QUOTED_STRING .
')';
604 $challenge =
"!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!";
605 if (!preg_match($challenge, $headerValue, $matches)) {
609 preg_match_all(
'!' . $authParam .
'!', $matches[0], $params);
610 $paramsAry = array();
611 $knownParams = array(
'realm',
'domain',
'nonce',
'opaque',
'stale',
613 for ($i = 0; $i < count($params[0]); $i++) {
615 if (in_array($params[1][$i], $knownParams)) {
616 if (
'"' == substr($params[2][$i], 0, 1)) {
617 $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
619 $paramsAry[$params[1][$i]] = $params[2][$i];
624 if (!empty($paramsAry[
'qop'])
625 && !in_array(
'auth', array_map(
'trim', explode(
',', $paramsAry[
'qop'])))
628 "Only 'auth' qop is currently supported in digest authentication, " .
629 "server requested '{$paramsAry['qop']}'"
633 if (!empty($paramsAry[
'algorithm']) &&
'MD5' != $paramsAry[
'algorithm']) {
635 "Only 'MD5' algorithm is currently supported in digest authentication, " .
636 "server requested '{$paramsAry['algorithm']}'"
653 $authParam =
'!(' . self::REGEXP_TOKEN .
')\\s*=\\s*(' .
654 self::REGEXP_TOKEN .
'|' . self::REGEXP_QUOTED_STRING .
')!';
655 $paramsAry = array();
657 preg_match_all($authParam, $headerValue, $params);
658 for ($i = 0; $i < count($params[0]); $i++) {
659 if (
'"' == substr($params[2][$i], 0, 1)) {
660 $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
662 $paramsAry[$params[1][$i]] = $params[2][$i];
666 if (!empty($paramsAry[
'nextnonce'])) {
667 $challenge[
'nonce'] = $paramsAry[
'nextnonce'];
668 $challenge[
'nc'] = 1;
685 if (
false !== ($q = strpos($url,
'?'))
686 && $this->request->getConfig(
'digest_compat_ie')
688 $url = substr($url, 0, $q);
691 $a1 = md5($user .
':' . $challenge[
'realm'] .
':' . $password);
692 $a2 = md5($this->request->getMethod() .
':' . $url);
694 if (empty($challenge[
'qop'])) {
695 $digest = md5($a1 .
':' . $challenge[
'nonce'] .
':' . $a2);
697 $challenge[
'cnonce'] =
'Req2.' . rand();
698 if (empty($challenge[
'nc'])) {
699 $challenge[
'nc'] = 1;
701 $nc = sprintf(
'%08x', $challenge[
'nc']++);
703 $a1 .
':' . $challenge[
'nonce'] .
':' . $nc .
':' .
704 $challenge[
'cnonce'] .
':auth:' . $a2
707 return 'Digest username="' . str_replace(array(
'\\',
'"'), array(
'\\\\',
'\\"'), $user) .
'", ' .
708 'realm="' . $challenge[
'realm'] .
'", ' .
709 'nonce="' . $challenge[
'nonce'] .
'", ' .
710 'uri="' . $url .
'", ' .
711 'response="' . $digest .
'"' .
712 (!empty($challenge[
'opaque'])?
713 ', opaque="' . $challenge[
'opaque'] .
'"':
715 (!empty($challenge[
'qop'])?
716 ', qop="auth", nc=' . $nc .
', cnonce="' . $challenge[
'cnonce'] .
'"':
731 if (!($auth = $this->request->getAuth())) {
734 switch ($auth[
'scheme']) {
736 $headers[
'authorization'] =
'Basic ' . base64_encode(
737 $auth[
'user'] .
':' . $auth[
'password']
742 unset($this->serverChallenge);
743 $fullUrl = (
'/' == $requestUrl[0])?
744 $this->request->getUrl()->getScheme() .
'://' .
745 $requestHost . $requestUrl:
747 foreach (array_keys(self::$challenges) as $key) {
748 if ($key == substr($fullUrl, 0, strlen($key))) {
750 $auth[
'user'], $auth[
'password'],
751 $requestUrl, self::$challenges[$key]
753 $this->serverChallenge =& self::$challenges[$key];
761 "Unknown HTTP authentication scheme '{$auth['scheme']}'"
776 if (!$this->request->getConfig(
'proxy_host')
777 || !($user = $this->request->getConfig(
'proxy_user'))
778 || (0 == strcasecmp(
'https', $this->request->getUrl()->getScheme())
784 $password = $this->request->getConfig(
'proxy_password');
785 switch ($this->request->getConfig(
'proxy_auth_scheme')) {
787 $headers[
'proxy-authorization'] =
'Basic ' . base64_encode(
788 $user .
':' . $password
793 unset($this->proxyChallenge);
794 $proxyUrl =
'proxy://' . $this->request->getConfig(
'proxy_host') .
795 ':' . $this->request->getConfig(
'proxy_port');
796 if (!empty(self::$challenges[$proxyUrl])) {
799 $requestUrl, self::$challenges[$proxyUrl]
801 $this->proxyChallenge =& self::$challenges[$proxyUrl];
807 "Unknown HTTP authentication scheme '" .
808 $this->request->getConfig(
'proxy_auth_scheme') .
"'"
822 $headers = $this->request->getHeaders();
823 $url = $this->request->getUrl();
825 $host = $url->getHost();
827 $defaultPort = 0 == strcasecmp($url->getScheme(),
'https')? 443: 80;
828 if (($port = $url->getPort()) && $port != $defaultPort || $connect) {
829 $host .=
':' . (empty($port)? $defaultPort: $port);
832 if (!isset($headers[
'host'])) {
833 $headers[
'host'] = $host;
840 if (!$this->request->getConfig(
'proxy_host')
841 ||
'http' != $this->request->getConfig(
'proxy_type')
842 || 0 == strcasecmp($url->getScheme(),
'https')
846 $requestUrl = $url->getScheme() .
'://' . $host;
848 $path = $url->getPath();
849 $query = $url->getQuery();
850 $requestUrl .= (empty($path)?
'/': $path) . (empty($query)?
'':
'?' . $query);
853 if (
'1.1' == $this->request->getConfig(
'protocol_version')
854 && extension_loaded(
'zlib') && !isset($headers[
'accept-encoding'])
856 $headers[
'accept-encoding'] =
'gzip, deflate';
858 if (($jar = $this->request->getCookieJar())
859 && ($cookies = $jar->getMatching($this->request->getUrl(),
true))
861 $headers[
'cookie'] = (empty($headers[
'cookie'])?
'': $headers[
'cookie'] .
'; ') . $cookies;
867 if (
'1.1' == $this->request->getConfig(
'protocol_version')) {
870 $this->expect100Continue =
false;
873 $headersStr = $this->request->getMethod() .
' ' . $requestUrl .
' HTTP/' .
874 $this->request->getConfig(
'protocol_version') .
"\r\n";
875 foreach ($headers as $name => $value) {
876 $canonicalName = implode(
'-', array_map(
'ucfirst', explode(
'-', $name)));
877 $headersStr .= $canonicalName .
': ' . $value .
"\r\n";
879 return $headersStr .
"\r\n";
900 $this->expect100Continue =
false;
901 $expectations = array();
902 if (isset($headers[
'expect'])) {
903 if (
'' === $headers[
'expect']) {
905 unset($headers[
'expect']);
909 $expectParam =
';\s*' . self::REGEXP_TOKEN .
'(?:\s*=\s*(?:'
910 . self::REGEXP_TOKEN .
'|'
911 . self::REGEXP_QUOTED_STRING .
'))?\s*';
912 $expectExtension = self::REGEXP_TOKEN .
'(?:\s*=\s*(?:'
913 . self::REGEXP_TOKEN .
'|'
914 . self::REGEXP_QUOTED_STRING .
')\s*(?:'
915 . $expectParam .
')*)?';
916 $expectItem =
'!(100-continue|' . $expectExtension .
')!A';
919 $length = strlen($headers[
'expect']);
921 while ($pos < $length) {
922 $pos += strspn($headers[
'expect'],
" \t", $pos);
923 if (
',' === substr($headers[
'expect'], $pos, 1)) {
927 } elseif (!preg_match($expectItem, $headers[
'expect'], $m, 0, $pos)) {
929 "Cannot parse value '{$headers['expect']}' of Expect header",
934 $pos += strlen($m[0]);
935 if (strcasecmp(
'100-continue', $m[0])) {
936 $expectations[] = $m[0];
942 if (1024 < $this->contentLength) {
943 $expectations[] =
'100-continue';
944 $this->expect100Continue =
true;
947 if (empty($expectations)) {
948 unset($headers[
'expect']);
950 $headers[
'expect'] = implode(
',', $expectations);
961 if (in_array($this->request->getMethod(), self::$bodyDisallowed)
962 || 0 == $this->contentLength
968 $bufferSize = $this->request->getConfig(
'buffer_size');
969 $headers = $this->request->getHeaders();
970 $chunked = isset($headers[
'transfer-encoding']);
971 while ($position < $this->contentLength) {
972 if (is_string($this->requestBody)) {
973 $str = substr($this->requestBody, $position, $bufferSize);
974 } elseif (is_resource($this->requestBody)) {
975 $str = fread($this->requestBody, $bufferSize);
977 $str = $this->requestBody->read($bufferSize);
980 $this->socket->write($str);
982 $this->socket->write(dechex(strlen($str)) .
"\r\n{$str}\r\n");
985 $this->request->setLastEvent(
'sentBodyPart', strlen($str));
986 $position += strlen($str);
991 $this->socket->write(
"0\r\n\r\n");
993 $this->request->setLastEvent(
'sentBody', $this->contentLength);
1004 $bufferSize = $this->request->getConfig(
'buffer_size');
1007 $timeout = $this->expect100Continue ? 1 : null;
1012 $this->socket->readLine($bufferSize, $timeout),
true, $this->request->getUrl()
1015 $headerLine = $this->socket->readLine($bufferSize);
1017 }
while (
'' != $headerLine);
1027 if ($this->expect100Continue && 100 == $response->getStatus()) {
1030 }
while (in_array($response->getStatus(), array(100, 101)));
1032 $this->request->setLastEvent(
'receivedHeaders', $response);
1037 && 200 <= $response->getStatus() && 300 > $response->getStatus())
1038 || in_array($response->getStatus(), array(204, 304))
1043 $chunked =
'chunked' == $response->getHeader(
'transfer-encoding');
1044 $length = $response->getHeader(
'content-length');
1046 if ($chunked || null === $length || 0 < intval($length)) {
1051 $toRead = ($chunked || null === $length)? null: $length;
1052 $this->chunkLength = 0;
1054 while (!$this->socket->eof() && (is_null($toRead) || 0 < $toRead)) {
1057 } elseif (is_null($toRead)) {
1058 $data = $this->socket->read($bufferSize);
1060 $data = $this->socket->read(min($toRead, $bufferSize));
1061 $toRead -= strlen($data);
1063 if (
'' == $data && (!$this->chunkLength || $this->socket->eof())) {
1068 if ($this->request->getConfig(
'store_body')) {
1069 $response->appendBody($data);
1071 if (!in_array($response->getHeader(
'content-encoding'), array(
'identity', null))) {
1072 $this->request->setLastEvent(
'receivedEncodedBodyPart', $data);
1074 $this->request->setLastEvent(
'receivedBodyPart', $data);
1080 $this->request->setLastEvent(
'receivedBody', $response);
1096 if (0 == $this->chunkLength) {
1097 $line = $this->socket->readLine($bufferSize);
1098 if (!preg_match(
'/^([0-9a-f]+)/i', $line, $matches)) {
1100 "Cannot decode chunked response, invalid chunk length '{$line}'",
1104 $this->chunkLength = hexdec($matches[1]);
1106 if (0 == $this->chunkLength) {
1107 $this->socket->readLine($bufferSize);
1112 $data = $this->socket->read(min($this->chunkLength, $bufferSize));
1113 $this->chunkLength -= strlen($data);
1114 if (0 == $this->chunkLength) {
1115 $this->socket->readLine($bufferSize);
parseDigestChallenge($headerValue)
const REGEXP_QUOTED_STRING
updateExpectHeader(&$headers)
shouldUseServerDigestAuth(HTTP_Request2_Response $response)
canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response)
setDeadline($deadline, $timeout)
createDigestResponse($user, $password, $url, &$challenge)
shouldUseProxyDigestAuth(HTTP_Request2_Response $response)
addAuthorizationHeader(&$headers, $requestHost, $requestUrl)
parseHeaderLine($headerLine)
getHeader($headerName=null)
calculateRequestLength(&$headers)
sendRequest(HTTP_Request2 $request)
addProxyAuthorizationHeader(&$headers, $requestUrl)
updateChallenge(&$challenge, $headerValue)
const OPTION_USE_BRACKETS
handleRedirect(HTTP_Request2 $request, HTTP_Request2_Response $response)