20     private $lastHeaders = [];
 
   33         if (isset($options[
'delay'])) {
 
   34             usleep($options[
'delay'] * 1000);
 
   45             if (0 === $request->
getBody()->getSize()) {
 
   46                 $request = $request->
withHeader(
'Content-Length', 
'0');
 
   49             return $this->createResponse(
 
   52                 $this->createStream($request, $options),
 
   57         } 
catch (\Exception $e) {
 
   59             $message = $e->getMessage();
 
   61             if (strpos($message, 
'getaddrinfo') 
 
   62                 || strpos($message, 
'Connection refused')
 
   63                 || strpos($message, 
"couldn't connect to host") 
 
   64                 || strpos($message, 
"connection attempt failed")
 
   69             $this->invokeStats($options, $request, $startTime, 
null, $e);
 
   71             return \GuzzleHttp\Promise\rejection_for($e);
 
   75     private function invokeStats(
 
   82         if (isset($options[
'on_stats'])) {
 
   90             call_user_func($options[
'on_stats'], $stats);
 
   94     private function createResponse(
 
   95         RequestInterface $request,
 
  100         $hdrs = $this->lastHeaders;
 
  101         $this->lastHeaders = [];
 
  102         $parts = explode(
' ', array_shift($hdrs), 3);
 
  103         $ver = explode(
'/', $parts[0])[1];
 
  105         $reason = isset($parts[2]) ? $parts[2] : 
null;
 
  106         $headers = \GuzzleHttp\headers_from_lines($hdrs);
 
  107         list($stream, $headers) = $this->checkDecode($options, $headers, $stream);
 
  111         if (strcasecmp(
'HEAD', $request->getMethod())) {
 
  112             $sink = $this->createSink($stream, $options);
 
  115         $response = 
new Psr7\Response($status, $headers, $sink, $ver, $reason);
 
  117         if (isset($options[
'on_headers'])) {
 
  119                 $options[
'on_headers']($response);
 
  120             } 
catch (\Exception $e) {
 
  121                 $msg = 
'An error was encountered during the on_headers event';
 
  122                 $ex = 
new RequestException($msg, $request, $response, $e);
 
  123                 return \GuzzleHttp\Promise\rejection_for($ex);
 
  129         if ($sink !== $stream) {
 
  133                 $response->getHeaderLine(
'Content-Length')
 
  137         $this->invokeStats($options, $request, $startTime, $response, 
null);
 
  139         return new FulfilledPromise($response);
 
  142     private function createSink(StreamInterface $stream, array $options)
 
  144         if (!empty($options[
'stream'])) {
 
  148         $sink = isset($options[
'sink'])
 
  150             : fopen(
'php://temp', 
'r+');
 
  152         return is_string($sink)
 
  153             ? 
new Psr7\LazyOpenStream($sink, 
'w+')
 
  157     private function checkDecode(array $options, array $headers, $stream)
 
  160         if (!empty($options[
'decode_content'])) {
 
  161             $normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
 
  162             if (isset($normalizedKeys[
'content-encoding'])) {
 
  163                 $encoding = $headers[$normalizedKeys[
'content-encoding']];
 
  164                 if ($encoding[0] === 
'gzip' || $encoding[0] === 
'deflate') {
 
  165                     $stream = 
new Psr7\InflateStream(
 
  168                     $headers[
'x-encoded-content-encoding']
 
  169                         = $headers[$normalizedKeys[
'content-encoding']];
 
  171                     unset($headers[$normalizedKeys[
'content-encoding']]);
 
  173                     if (isset($normalizedKeys[
'content-length'])) {
 
  174                         $headers[
'x-encoded-content-length']
 
  175                             = $headers[$normalizedKeys[
'content-length']];
 
  177                         $length = (int) $stream->getSize();
 
  179                             unset($headers[$normalizedKeys[
'content-length']]);
 
  181                             $headers[$normalizedKeys[
'content-length']] = [$length];
 
  188         return [$stream, $headers];
 
  202     private function drain(
 
  203         StreamInterface $source,
 
  204         StreamInterface $sink,
 
  214             (strlen($contentLength) > 0 && (
int) $contentLength > 0) ? (
int) $contentLength : -1
 
  231     private function createResource(callable $callback)
 
  234         set_error_handler(
function ($_, $msg, $file, $line) use (&$errors) {
 
  243         $resource = $callback();
 
  244         restore_error_handler();
 
  247             $message = 
'Error creating resource: ';
 
  248             foreach ($errors as $err) {
 
  249                 foreach ($err as $key => $value) {
 
  250                     $message .= 
"[$key] $value" . PHP_EOL;
 
  253             throw new \RuntimeException(trim($message));
 
  259     private function createStream(RequestInterface $request, array $options)
 
  263             $methods = array_flip(get_class_methods(__CLASS__));
 
  268         if ($request->getProtocolVersion() == 
'1.1' 
  269             && !$request->hasHeader(
'Connection')
 
  271             $request = $request->withHeader(
'Connection', 
'close');
 
  275         if (!isset($options[
'verify'])) {
 
  276             $options[
'verify'] = 
true;
 
  280         $context = $this->getDefaultContext($request);
 
  282         if (isset($options[
'on_headers']) && !is_callable($options[
'on_headers'])) {
 
  283             throw new \InvalidArgumentException(
'on_headers must be callable');
 
  286         if (!empty($options)) {
 
  287             foreach ($options as $key => $value) {
 
  288                 $method = 
"add_{$key}";
 
  289                 if (isset($methods[$method])) {
 
  290                     $this->{$method}($request, $context, $value, $params);
 
  295         if (isset($options[
'stream_context'])) {
 
  296             if (!is_array($options[
'stream_context'])) {
 
  297                 throw new \InvalidArgumentException(
'stream_context must be an array');
 
  299             $context = array_replace_recursive(
 
  301                 $options[
'stream_context']
 
  306         if (isset($options[
'auth'])
 
  307             && is_array($options[
'auth'])
 
  308             && isset($options[
'auth'][2])
 
  309             && 
'ntlm' == $options[
'auth'][2]
 
  311             throw new \InvalidArgumentException(
'Microsoft NTLM authentication only supported with curl handler');
 
  314         $uri = $this->resolveHost($request, $options);
 
  316         $context = $this->createResource(
 
  317             function () use ($context, $params) {
 
  318                 return stream_context_create($context, $params);
 
  322         return $this->createResource(
 
  323             function () use ($uri, &$http_response_header, $context, $options) {
 
  324                 $resource = fopen((
string) $uri, 
'r', 
null, $context);
 
  325                 $this->lastHeaders = $http_response_header;
 
  327                 if (isset($options[
'read_timeout'])) {
 
  328                     $readTimeout = $options[
'read_timeout'];
 
  329                     $sec = (int) $readTimeout;
 
  330                     $usec = ($readTimeout - $sec) * 100000;
 
  331                     stream_set_timeout($resource, $sec, $usec);
 
  339     private function resolveHost(RequestInterface $request, array $options)
 
  341         $uri = $request->getUri();
 
  343         if (isset($options[
'force_ip_resolve']) && !filter_var($uri->getHost(), FILTER_VALIDATE_IP)) {
 
  344             if (
'v4' === $options[
'force_ip_resolve']) {
 
  345                 $records = dns_get_record($uri->getHost(), DNS_A);
 
  346                 if (!isset($records[0][
'ip'])) {
 
  347                     throw new ConnectException(
 
  349                             "Could not resolve IPv4 address for host '%s'",
 
  355                 $uri = $uri->withHost($records[0][
'ip']);
 
  356             } elseif (
'v6' === $options[
'force_ip_resolve']) {
 
  357                 $records = dns_get_record($uri->getHost(), DNS_AAAA);
 
  358                 if (!isset($records[0][
'ipv6'])) {
 
  359                     throw new ConnectException(
 
  361                             "Could not resolve IPv6 address for host '%s'",
 
  367                 $uri = $uri->withHost(
'[' . $records[0][
'ipv6'] . 
']');
 
  374     private function getDefaultContext(RequestInterface $request)
 
  377         foreach ($request->getHeaders() as $name => $value) {
 
  378             foreach ($value as $val) {
 
  379                 $headers .= 
"$name: $val\r\n";
 
  385                 'method'           => $request->getMethod(),
 
  386                 'header'           => $headers,
 
  387                 'protocol_version' => $request->getProtocolVersion(),
 
  388                 'ignore_errors'    => 
true,
 
  389                 'follow_location'  => 0,
 
  393         $body = (string) $request->getBody();
 
  396             $context[
'http'][
'content'] = $body;
 
  398             if (!$request->hasHeader(
'Content-Type')) {
 
  399                 $context[
'http'][
'header'] .= 
"Content-Type:\r\n";
 
  403         $context[
'http'][
'header'] = rtrim($context[
'http'][
'header']);
 
  408     private function add_proxy(RequestInterface $request, &$options, $value, &$params)
 
  410         if (!is_array($value)) {
 
  411             $options[
'http'][
'proxy'] = $value;
 
  413             $scheme = $request->getUri()->getScheme();
 
  414             if (isset($value[$scheme])) {
 
  415                 if (!isset($value[
'no'])
 
  417                         $request->getUri()->getHost(),
 
  421                     $options[
'http'][
'proxy'] = $value[$scheme];
 
  427     private function add_timeout(RequestInterface $request, &$options, $value, &$params)
 
  430             $options[
'http'][
'timeout'] = $value;
 
  434     private function add_verify(RequestInterface $request, &$options, $value, &$params)
 
  436         if ($value === 
true) {
 
  439             if (PHP_VERSION_ID < 50600) {
 
  440                 $options[
'ssl'][
'cafile'] = \GuzzleHttp\default_ca_bundle();
 
  442         } elseif (is_string($value)) {
 
  443             $options[
'ssl'][
'cafile'] = $value;
 
  444             if (!file_exists($value)) {
 
  445                 throw new \RuntimeException(
"SSL CA bundle not found: $value");
 
  447         } elseif ($value === 
false) {
 
  448             $options[
'ssl'][
'verify_peer'] = 
false;
 
  449             $options[
'ssl'][
'verify_peer_name'] = 
false;
 
  452             throw new \InvalidArgumentException(
'Invalid verify request option');
 
  455         $options[
'ssl'][
'verify_peer'] = 
true;
 
  456         $options[
'ssl'][
'verify_peer_name'] = 
true;
 
  457         $options[
'ssl'][
'allow_self_signed'] = 
false;
 
  460     private function add_cert(RequestInterface $request, &$options, $value, &$params)
 
  462         if (is_array($value)) {
 
  463             $options[
'ssl'][
'passphrase'] = $value[1];
 
  467         if (!file_exists($value)) {
 
  468             throw new \RuntimeException(
"SSL certificate not found: {$value}");
 
  471         $options[
'ssl'][
'local_cert'] = $value;
 
  474     private function add_progress(RequestInterface $request, &$options, $value, &$params)
 
  476         $this->addNotification(
 
  478             function ($code, $a, $b, $c, $transferred, $total) use ($value) {
 
  479                 if ($code == STREAM_NOTIFY_PROGRESS) {
 
  480                     $value($total, $transferred, 
null, 
null);
 
  486     private function add_debug(RequestInterface $request, &$options, $value, &$params)
 
  488         if ($value === 
false) {
 
  493             STREAM_NOTIFY_CONNECT       => 
'CONNECT',
 
  494             STREAM_NOTIFY_AUTH_REQUIRED => 
'AUTH_REQUIRED',
 
  495             STREAM_NOTIFY_AUTH_RESULT   => 
'AUTH_RESULT',
 
  496             STREAM_NOTIFY_MIME_TYPE_IS  => 
'MIME_TYPE_IS',
 
  497             STREAM_NOTIFY_FILE_SIZE_IS  => 
'FILE_SIZE_IS',
 
  498             STREAM_NOTIFY_REDIRECTED    => 
'REDIRECTED',
 
  499             STREAM_NOTIFY_PROGRESS      => 
'PROGRESS',
 
  500             STREAM_NOTIFY_FAILURE       => 
'FAILURE',
 
  501             STREAM_NOTIFY_COMPLETED     => 
'COMPLETED',
 
  502             STREAM_NOTIFY_RESOLVE       => 
'RESOLVE',
 
  504         static $args = [
'severity', 
'message', 
'message_code',
 
  505             'bytes_transferred', 
'bytes_max'];
 
  507         $value = \GuzzleHttp\debug_resource($value);
 
  508         $ident = $request->getMethod() . 
' ' . $request->getUri()->withFragment(
'');
 
  509         $this->addNotification(
 
  511             function () use ($ident, $value, $map, $args) {
 
  512                 $passed = func_get_args();
 
  513                 $code = array_shift($passed);
 
  514                 fprintf($value, 
'<%s> [%s] ', $ident, $map[$code]);
 
  515                 foreach (array_filter($passed) as $i => $v) {
 
  516                     fwrite($value, $args[$i] . 
': "' . $v . 
'" ');
 
  518                 fwrite($value, 
"\n");
 
  523     private function addNotification(array &$params, callable $notify)
 
  526         if (!isset($params[
'notification'])) {
 
  527             $params[
'notification'] = $notify;
 
  529             $params[
'notification'] = $this->callArray([
 
  530                 $params[
'notification'],
 
  536     private function callArray(array $functions)
 
  538         return function () use ($functions) {
 
  539             $args = func_get_args();
 
  540             foreach ($functions as $fn) {
 
  541                 call_user_func_array($fn, $args);