1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491:
<?php
namespace GuzzleHttp\Handler;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Promise\FulfilledPromise;
use GuzzleHttp\Promise\RejectedPromise;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Psr7;
use GuzzleHttp\TransferStats;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
class StreamHandler
{
private $lastHeaders = [];
public function __invoke(RequestInterface $request, array $options)
{
if (isset($options['delay'])) {
usleep($options['delay'] * 1000);
}
$startTime = isset($options['on_stats']) ? microtime(true) : null;
try {
$request = $request->withoutHeader('Expect');
if (0 === $request->getBody()->getSize()) {
$request = $request->withHeader('Content-Length', 0);
}
return $this->createResponse(
$request,
$options,
$this->createStream($request, $options),
$startTime
);
} catch (\InvalidArgumentException $e) {
throw $e;
} catch (\Exception $e) {
$message = $e->getMessage();
if (strpos($message, 'getaddrinfo')
|| strpos($message, 'Connection refused')
|| strpos($message, "couldn't connect to host")
) {
$e = new ConnectException($e->getMessage(), $request, $e);
}
$e = RequestException::wrapException($request, $e);
$this->invokeStats($options, $request, $startTime, null, $e);
return new RejectedPromise($e);
}
}
private function invokeStats(
array $options,
RequestInterface $request,
$startTime,
ResponseInterface $response = null,
$error = null
) {
if (isset($options['on_stats'])) {
$stats = new TransferStats(
$request,
$response,
microtime(true) - $startTime,
$error,
[]
);
call_user_func($options['on_stats'], $stats);
}
}
private function createResponse(
RequestInterface $request,
array $options,
$stream,
$startTime
) {
$hdrs = $this->lastHeaders;
$this->lastHeaders = [];
$parts = explode(' ', array_shift($hdrs), 3);
$ver = explode('/', $parts[0])[1];
$status = $parts[1];
$reason = isset($parts[2]) ? $parts[2] : null;
$headers = \GuzzleHttp\headers_from_lines($hdrs);
list ($stream, $headers) = $this->checkDecode($options, $headers, $stream);
$stream = Psr7\stream_for($stream);
$sink = $stream;
if (strcasecmp('HEAD', $request->getMethod())) {
$sink = $this->createSink($stream, $options);
}
$response = new Psr7\Response($status, $headers, $sink, $ver, $reason);
if (isset($options['on_headers'])) {
try {
$options['on_headers']($response);
} catch (\Exception $e) {
$msg = 'An error was encountered during the on_headers event';
$ex = new RequestException($msg, $request, $response, $e);
return new RejectedPromise($ex);
}
}
if ($sink !== $stream) {
$this->drain(
$stream,
$sink,
$response->getHeaderLine('Content-Length')
);
}
$this->invokeStats($options, $request, $startTime, $response, null);
return new FulfilledPromise($response);
}
private function createSink(StreamInterface $stream, array $options)
{
if (!empty($options['stream'])) {
return $stream;
}
$sink = isset($options['sink'])
? $options['sink']
: fopen('php://temp', 'r+');
return is_string($sink)
? new Psr7\LazyOpenStream($sink, 'w+')
: Psr7\stream_for($sink);
}
private function checkDecode(array $options, array $headers, $stream)
{
if (!empty($options['decode_content'])) {
$normalizedKeys = \GuzzleHttp\normalize_header_keys($headers);
if (isset($normalizedKeys['content-encoding'])) {
$encoding = $headers[$normalizedKeys['content-encoding']];
if ($encoding[0] === 'gzip' || $encoding[0] === 'deflate') {
$stream = new Psr7\InflateStream(
Psr7\stream_for($stream)
);
$headers['x-encoded-content-encoding']
= $headers[$normalizedKeys['content-encoding']];
unset($headers[$normalizedKeys['content-encoding']]);
if (isset($normalizedKeys['content-length'])) {
$headers['x-encoded-content-length']
= $headers[$normalizedKeys['content-length']];
$length = (int) $stream->getSize();
if ($length === 0) {
unset($headers[$normalizedKeys['content-length']]);
} else {
$headers[$normalizedKeys['content-length']] = [$length];
}
}
}
}
}
return [$stream, $headers];
}
private function drain(
StreamInterface $source,
StreamInterface $sink,
$contentLength
) {
Psr7\copy_to_stream(
$source,
$sink,
(strlen($contentLength) > 0 && (int) $contentLength > 0) ? (int) $contentLength : -1
);
$sink->seek(0);
$source->close();
return $sink;
}
private function createResource(callable $callback)
{
$errors = null;
set_error_handler(function ($_, $msg, $file, $line) use (&$errors) {
$errors[] = [
'message' => $msg,
'file' => $file,
'line' => $line
];
return true;
});
$resource = $callback();
restore_error_handler();
if (!$resource) {
$message = 'Error creating resource: ';
foreach ($errors as $err) {
foreach ($err as $key => $value) {
$message .= "[$key] $value" . PHP_EOL;
}
}
throw new \RuntimeException(trim($message));
}
return $resource;
}
private function createStream(RequestInterface $request, array $options)
{
static $methods;
if (!$methods) {
$methods = array_flip(get_class_methods(__CLASS__));
}
if ($request->getProtocolVersion() == '1.1'
&& !$request->hasHeader('Connection')
) {
$request = $request->withHeader('Connection', 'close');
}
if (!isset($options['verify'])) {
$options['verify'] = true;
}
$params = [];
$context = $this->getDefaultContext($request, $options);
if (isset($options['on_headers']) && !is_callable($options['on_headers'])) {
throw new \InvalidArgumentException('on_headers must be callable');
}
if (!empty($options)) {
foreach ($options as $key => $value) {
$method = "add_{$key}";
if (isset($methods[$method])) {
$this->{$method}($request, $context, $value, $params);
}
}
}
if (isset($options['stream_context'])) {
if (!is_array($options['stream_context'])) {
throw new \InvalidArgumentException('stream_context must be an array');
}
$context = array_replace_recursive(
$context,
$options['stream_context']
);
}
$context = $this->createResource(
function () use ($context, $params) {
return stream_context_create($context, $params);
}
);
return $this->createResource(
function () use ($request, &$http_response_header, $context) {
$resource = fopen((string) $request->getUri()->withFragment(''), 'r', null, $context);
$this->lastHeaders = $http_response_header;
return $resource;
}
);
}
private function getDefaultContext(RequestInterface $request)
{
$headers = '';
foreach ($request->getHeaders() as $name => $value) {
foreach ($value as $val) {
$headers .= "$name: $val\r\n";
}
}
$context = [
'http' => [
'method' => $request->getMethod(),
'header' => $headers,
'protocol_version' => $request->getProtocolVersion(),
'ignore_errors' => true,
'follow_location' => 0,
],
];
$body = (string) $request->getBody();
if (!empty($body)) {
$context['http']['content'] = $body;
if (!$request->hasHeader('Content-Type')) {
$context['http']['header'] .= "Content-Type:\r\n";
}
}
$context['http']['header'] = rtrim($context['http']['header']);
return $context;
}
private function add_proxy(RequestInterface $request, &$options, $value, &$params)
{
if (!is_array($value)) {
$options['http']['proxy'] = $value;
} else {
$scheme = $request->getUri()->getScheme();
if (isset($value[$scheme])) {
if (!isset($value['no'])
|| !\GuzzleHttp\is_host_in_noproxy(
$request->getUri()->getHost(),
$value['no']
)
) {
$options['http']['proxy'] = $value[$scheme];
}
}
}
}
private function add_timeout(RequestInterface $request, &$options, $value, &$params)
{
if ($value > 0) {
$options['http']['timeout'] = $value;
}
}
private function add_verify(RequestInterface $request, &$options, $value, &$params)
{
if ($value === true) {
if (PHP_VERSION_ID < 50600) {
$options['ssl']['cafile'] = \GuzzleHttp\default_ca_bundle();
}
} elseif (is_string($value)) {
$options['ssl']['cafile'] = $value;
if (!file_exists($value)) {
throw new \RuntimeException("SSL CA bundle not found: $value");
}
} elseif ($value === false) {
$options['ssl']['verify_peer'] = false;
$options['ssl']['verify_peer_name'] = false;
return;
} else {
throw new \InvalidArgumentException('Invalid verify request option');
}
$options['ssl']['verify_peer'] = true;
$options['ssl']['verify_peer_name'] = true;
$options['ssl']['allow_self_signed'] = false;
}
private function add_cert(RequestInterface $request, &$options, $value, &$params)
{
if (is_array($value)) {
$options['ssl']['passphrase'] = $value[1];
$value = $value[0];
}
if (!file_exists($value)) {
throw new \RuntimeException("SSL certificate not found: {$value}");
}
$options['ssl']['local_cert'] = $value;
}
private function add_progress(RequestInterface $request, &$options, $value, &$params)
{
$this->addNotification(
$params,
function ($code, $a, $b, $c, $transferred, $total) use ($value) {
if ($code == STREAM_NOTIFY_PROGRESS) {
$value($total, $transferred, null, null);
}
}
);
}
private function add_debug(RequestInterface $request, &$options, $value, &$params)
{
if ($value === false) {
return;
}
static $map = [
STREAM_NOTIFY_CONNECT => 'CONNECT',
STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED',
STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT',
STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS',
STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS',
STREAM_NOTIFY_REDIRECTED => 'REDIRECTED',
STREAM_NOTIFY_PROGRESS => 'PROGRESS',
STREAM_NOTIFY_FAILURE => 'FAILURE',
STREAM_NOTIFY_COMPLETED => 'COMPLETED',
STREAM_NOTIFY_RESOLVE => 'RESOLVE',
];
static $args = ['severity', 'message', 'message_code',
'bytes_transferred', 'bytes_max'];
$value = \GuzzleHttp\debug_resource($value);
$ident = $request->getMethod() . ' ' . $request->getUri()->withFragment('');
$this->addNotification(
$params,
function () use ($ident, $value, $map, $args) {
$passed = func_get_args();
$code = array_shift($passed);
fprintf($value, '<%s> [%s] ', $ident, $map[$code]);
foreach (array_filter($passed) as $i => $v) {
fwrite($value, $args[$i] . ': "' . $v . '" ');
}
fwrite($value, "\n");
}
);
}
private function addNotification(array &$params, callable $notify)
{
if (!isset($params['notification'])) {
$params['notification'] = $notify;
} else {
$params['notification'] = $this->callArray([
$params['notification'],
$notify
]);
}
}
private function callArray(array $functions)
{
return function () use ($functions) {
$args = func_get_args();
foreach ($functions as $fn) {
call_user_func_array($fn, $args);
}
};
}
}