feat: initial ACRIB WordPress deployment
- WordPress 6.9.4 (es_ES) with Kadence theme - Homepage: Hero, La Asociación, Pilares, Beneficios, Eventos, Miembros, Hazte Miembro, Contacto - Brand identity: #13294b navy, #a12932 burgundy, #c69c48 gold - Fonts: Raleway (headings) + Source Sans 3 (body) + Lato (UI) - Plugins: Kadence Blocks, Polylang, Contact Form 7 - Custom CSS with full brand styling and responsive layout - HTTPS enforced via wp-config.php proxy detection
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
The changelog is maintained for all Symfony contracts at the following URL:
|
||||
https://github.com/symfony/contracts/blob/main/CHANGELOG.md
|
||||
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/deprecation-contracts/LICENSE
vendored
Normal file
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/deprecation-contracts/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2020-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
26
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/deprecation-contracts/README.md
vendored
Normal file
26
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/deprecation-contracts/README.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
Symfony Deprecation Contracts
|
||||
=============================
|
||||
|
||||
A generic function and convention to trigger deprecation notices.
|
||||
|
||||
This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
|
||||
|
||||
By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
|
||||
the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
|
||||
|
||||
The function requires at least 3 arguments:
|
||||
- the name of the Composer package that is triggering the deprecation
|
||||
- the version of the package that introduced the deprecation
|
||||
- the message of the deprecation
|
||||
- more arguments can be provided: they will be inserted in the message using `printf()` formatting
|
||||
|
||||
Example:
|
||||
```php
|
||||
trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
|
||||
```
|
||||
|
||||
This will generate the following message:
|
||||
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
|
||||
|
||||
While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
|
||||
`function trigger_deprecation() {}` in your application.
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
if (!function_exists('kadence_blocks_trigger_deprecation')) {
|
||||
/**
|
||||
* Triggers a silenced deprecation notice.
|
||||
*
|
||||
* @param string $package The name of the Composer package that is triggering the deprecation
|
||||
* @param string $version The version of the package that introduced the deprecation
|
||||
* @param string $message The message of the deprecation
|
||||
* @param mixed ...$args Values to insert in the message using printf() formatting
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
function kadence_blocks_trigger_deprecation(string $package, string $version, string $message, ...$args): void
|
||||
{
|
||||
@trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
82
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/CHANGELOG.md
vendored
Normal file
82
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Add `Path` class
|
||||
* Add `$lock` argument to `Filesystem::appendToFile()`
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* `Filesystem::dumpFile()` and `appendToFile()` don't accept arrays anymore
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* support for passing a `null` value to `Filesystem::isAbsolutePath()` is deprecated and will be removed in 5.0
|
||||
* `tempnam()` now accepts a third argument `$suffix`.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* support for passing arrays to `Filesystem::dumpFile()` is deprecated and will be removed in 5.0
|
||||
* support for passing arrays to `Filesystem::appendToFile()` is deprecated and will be removed in 5.0
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* removed `LockHandler`
|
||||
* Support for passing relative paths to `Filesystem::makePathRelative()` has been removed.
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* support for passing relative paths to `Filesystem::makePathRelative()` is deprecated and will be removed in 4.0
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* added `appendToFile()` to append contents to existing files
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
* added `readlink()` as a platform independent method to read links
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* removed `$mode` argument from `Filesystem::dumpFile()`
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* added tempnam() a stream aware version of PHP's native tempnam()
|
||||
|
||||
2.6.0
|
||||
-----
|
||||
|
||||
* added LockHandler
|
||||
|
||||
2.3.12
|
||||
------
|
||||
|
||||
* deprecated dumpFile() file mode argument.
|
||||
|
||||
2.3.0
|
||||
-----
|
||||
|
||||
* added the dumpFile() method to atomically write files
|
||||
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* added a delete option for the mirror() method
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* 24eb396 : BC Break : mkdir() function now throws exception in case of failure instead of returning Boolean value
|
||||
* created the component
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception;
|
||||
|
||||
/**
|
||||
* Exception interface for all exceptions thrown by the component.
|
||||
*
|
||||
* @author Romain Neutron <imprec@gmail.com>
|
||||
*/
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception;
|
||||
|
||||
/**
|
||||
* Exception class thrown when a file couldn't be found.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
|
||||
*/
|
||||
class FileNotFoundException extends IOException
|
||||
{
|
||||
public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
|
||||
{
|
||||
if (null === $message) {
|
||||
if (null === $path) {
|
||||
$message = 'File could not be found.';
|
||||
} else {
|
||||
$message = sprintf('File "%s" could not be found.', $path);
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($message, $code, $previous, $path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception;
|
||||
|
||||
/**
|
||||
* Exception class thrown when a filesystem operation failure happens.
|
||||
*
|
||||
* @author Romain Neutron <imprec@gmail.com>
|
||||
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class IOException extends \RuntimeException implements IOExceptionInterface
|
||||
{
|
||||
private $path;
|
||||
|
||||
public function __construct(string $message, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
parent::__construct($message, $code, $previous);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception;
|
||||
|
||||
/**
|
||||
* IOException interface for file and input/output stream related exceptions thrown by the component.
|
||||
*
|
||||
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
|
||||
*/
|
||||
interface IOExceptionInterface extends ExceptionInterface
|
||||
{
|
||||
/**
|
||||
* Returns the associated path for the exception.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPath();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception;
|
||||
|
||||
/**
|
||||
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception;
|
||||
|
||||
/**
|
||||
* @author Théo Fidry <theo.fidry@gmail.com>
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
790
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/Filesystem.php
vendored
Normal file
790
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/Filesystem.php
vendored
Normal file
@@ -0,0 +1,790 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception\FileNotFoundException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception\IOException;
|
||||
|
||||
/**
|
||||
* Provides basic utility to manipulate the file system.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Filesystem
|
||||
{
|
||||
private static $lastError;
|
||||
|
||||
/**
|
||||
* Copies a file.
|
||||
*
|
||||
* If the target file is older than the origin file, it's always overwritten.
|
||||
* If the target file is newer, it is overwritten only when the
|
||||
* $overwriteNewerFiles option is set to true.
|
||||
*
|
||||
* @throws FileNotFoundException When originFile doesn't exist
|
||||
* @throws IOException When copy fails
|
||||
*/
|
||||
public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false)
|
||||
{
|
||||
$originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
|
||||
if ($originIsLocal && !is_file($originFile)) {
|
||||
throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
|
||||
}
|
||||
|
||||
$this->mkdir(\dirname($targetFile));
|
||||
|
||||
$doCopy = true;
|
||||
if (!$overwriteNewerFiles && !parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) {
|
||||
$doCopy = filemtime($originFile) > filemtime($targetFile);
|
||||
}
|
||||
|
||||
if ($doCopy) {
|
||||
// https://bugs.php.net/64634
|
||||
if (!$source = self::box('fopen', $originFile, 'r')) {
|
||||
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile);
|
||||
}
|
||||
|
||||
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
|
||||
if (!$target = self::box('fopen', $targetFile, 'w', false, stream_context_create(['ftp' => ['overwrite' => true]]))) {
|
||||
throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing: ', $originFile, $targetFile).self::$lastError, 0, null, $originFile);
|
||||
}
|
||||
|
||||
$bytesCopied = stream_copy_to_stream($source, $target);
|
||||
fclose($source);
|
||||
fclose($target);
|
||||
unset($source, $target);
|
||||
|
||||
if (!is_file($targetFile)) {
|
||||
throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
|
||||
}
|
||||
|
||||
if ($originIsLocal) {
|
||||
// Like `cp`, preserve executable permission bits
|
||||
self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
|
||||
|
||||
// Like `cp`, preserve the file modification time
|
||||
self::box('touch', $targetFile, filemtime($originFile));
|
||||
|
||||
if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
|
||||
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory recursively.
|
||||
*
|
||||
* @param string|iterable $dirs The directory path
|
||||
*
|
||||
* @throws IOException On any directory creation failure
|
||||
*/
|
||||
public function mkdir($dirs, int $mode = 0777)
|
||||
{
|
||||
foreach ($this->toIterable($dirs) as $dir) {
|
||||
if (is_dir($dir)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) {
|
||||
throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the existence of files or directories.
|
||||
*
|
||||
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($files)
|
||||
{
|
||||
$maxPathLength = \PHP_MAXPATHLEN - 2;
|
||||
|
||||
foreach ($this->toIterable($files) as $file) {
|
||||
if (\strlen($file) > $maxPathLength) {
|
||||
throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
|
||||
}
|
||||
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets access and modification time of file.
|
||||
*
|
||||
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
|
||||
* @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
|
||||
* @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
|
||||
*
|
||||
* @throws IOException When touch fails
|
||||
*/
|
||||
public function touch($files, ?int $time = null, ?int $atime = null)
|
||||
{
|
||||
foreach ($this->toIterable($files) as $file) {
|
||||
if (!($time ? self::box('touch', $file, $time, $atime) : self::box('touch', $file))) {
|
||||
throw new IOException(sprintf('Failed to touch "%s": ', $file).self::$lastError, 0, null, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes files or directories.
|
||||
*
|
||||
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
|
||||
*
|
||||
* @throws IOException When removal fails
|
||||
*/
|
||||
public function remove($files)
|
||||
{
|
||||
if ($files instanceof \Traversable) {
|
||||
$files = iterator_to_array($files, false);
|
||||
} elseif (!\is_array($files)) {
|
||||
$files = [$files];
|
||||
}
|
||||
|
||||
self::doRemove($files, false);
|
||||
}
|
||||
|
||||
private static function doRemove(array $files, bool $isRecursive): void
|
||||
{
|
||||
$files = array_reverse($files);
|
||||
foreach ($files as $file) {
|
||||
if (is_link($file)) {
|
||||
// See https://bugs.php.net/52176
|
||||
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
|
||||
throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError);
|
||||
}
|
||||
} elseif (is_dir($file)) {
|
||||
if (!$isRecursive) {
|
||||
$tmpName = \dirname(realpath($file)).'/.!'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-!'));
|
||||
|
||||
if (file_exists($tmpName)) {
|
||||
try {
|
||||
self::doRemove([$tmpName], true);
|
||||
} catch (IOException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) {
|
||||
$origFile = $file;
|
||||
$file = $tmpName;
|
||||
} else {
|
||||
$origFile = null;
|
||||
}
|
||||
}
|
||||
|
||||
$files = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS);
|
||||
self::doRemove(iterator_to_array($files, true), true);
|
||||
|
||||
if (!self::box('rmdir', $file) && file_exists($file) && !$isRecursive) {
|
||||
$lastError = self::$lastError;
|
||||
|
||||
if (null !== $origFile && self::box('rename', $file, $origFile)) {
|
||||
$file = $origFile;
|
||||
}
|
||||
|
||||
throw new IOException(sprintf('Failed to remove directory "%s": ', $file).$lastError);
|
||||
}
|
||||
} elseif (!self::box('unlink', $file) && ((self::$lastError && str_contains(self::$lastError, 'Permission denied')) || file_exists($file))) {
|
||||
throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change mode for an array of files or directories.
|
||||
*
|
||||
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode
|
||||
* @param int $mode The new mode (octal)
|
||||
* @param int $umask The mode mask (octal)
|
||||
* @param bool $recursive Whether change the mod recursively or not
|
||||
*
|
||||
* @throws IOException When the change fails
|
||||
*/
|
||||
public function chmod($files, int $mode, int $umask = 0000, bool $recursive = false)
|
||||
{
|
||||
foreach ($this->toIterable($files) as $file) {
|
||||
if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && !self::box('chmod', $file, $mode & ~$umask)) {
|
||||
throw new IOException(sprintf('Failed to chmod file "%s": ', $file).self::$lastError, 0, null, $file);
|
||||
}
|
||||
if ($recursive && is_dir($file) && !is_link($file)) {
|
||||
$this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the owner of an array of files or directories.
|
||||
*
|
||||
* This method always throws on Windows, as the underlying PHP function is not supported.
|
||||
*
|
||||
* @see https://www.php.net/chown
|
||||
*
|
||||
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner
|
||||
* @param string|int $user A user name or number
|
||||
* @param bool $recursive Whether change the owner recursively or not
|
||||
*
|
||||
* @throws IOException When the change fails
|
||||
*/
|
||||
public function chown($files, $user, bool $recursive = false)
|
||||
{
|
||||
foreach ($this->toIterable($files) as $file) {
|
||||
if ($recursive && is_dir($file) && !is_link($file)) {
|
||||
$this->chown(new \FilesystemIterator($file), $user, true);
|
||||
}
|
||||
if (is_link($file) && \function_exists('lchown')) {
|
||||
if (!self::box('lchown', $file, $user)) {
|
||||
throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
|
||||
}
|
||||
} else {
|
||||
if (!self::box('chown', $file, $user)) {
|
||||
throw new IOException(sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the group of an array of files or directories.
|
||||
*
|
||||
* This method always throws on Windows, as the underlying PHP function is not supported.
|
||||
*
|
||||
* @see https://www.php.net/chgrp
|
||||
*
|
||||
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group
|
||||
* @param string|int $group A group name or number
|
||||
* @param bool $recursive Whether change the group recursively or not
|
||||
*
|
||||
* @throws IOException When the change fails
|
||||
*/
|
||||
public function chgrp($files, $group, bool $recursive = false)
|
||||
{
|
||||
foreach ($this->toIterable($files) as $file) {
|
||||
if ($recursive && is_dir($file) && !is_link($file)) {
|
||||
$this->chgrp(new \FilesystemIterator($file), $group, true);
|
||||
}
|
||||
if (is_link($file) && \function_exists('lchgrp')) {
|
||||
if (!self::box('lchgrp', $file, $group)) {
|
||||
throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
|
||||
}
|
||||
} else {
|
||||
if (!self::box('chgrp', $file, $group)) {
|
||||
throw new IOException(sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a file or a directory.
|
||||
*
|
||||
* @throws IOException When target file or directory already exists
|
||||
* @throws IOException When origin cannot be renamed
|
||||
*/
|
||||
public function rename(string $origin, string $target, bool $overwrite = false)
|
||||
{
|
||||
// we check that target does not exist
|
||||
if (!$overwrite && $this->isReadable($target)) {
|
||||
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
|
||||
}
|
||||
|
||||
if (!self::box('rename', $origin, $target)) {
|
||||
if (is_dir($origin)) {
|
||||
// See https://bugs.php.net/54097 & https://php.net/rename#113943
|
||||
$this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]);
|
||||
$this->remove($origin);
|
||||
|
||||
return;
|
||||
}
|
||||
throw new IOException(sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells whether a file exists and is readable.
|
||||
*
|
||||
* @throws IOException When windows path is longer than 258 characters
|
||||
*/
|
||||
private function isReadable(string $filename): bool
|
||||
{
|
||||
$maxPathLength = \PHP_MAXPATHLEN - 2;
|
||||
|
||||
if (\strlen($filename) > $maxPathLength) {
|
||||
throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
|
||||
}
|
||||
|
||||
return is_readable($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a symbolic link or copy a directory.
|
||||
*
|
||||
* @throws IOException When symlink fails
|
||||
*/
|
||||
public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false)
|
||||
{
|
||||
self::assertFunctionExists('symlink');
|
||||
|
||||
if ('\\' === \DIRECTORY_SEPARATOR) {
|
||||
$originDir = strtr($originDir, '/', '\\');
|
||||
$targetDir = strtr($targetDir, '/', '\\');
|
||||
|
||||
if ($copyOnWindows) {
|
||||
$this->mirror($originDir, $targetDir);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->mkdir(\dirname($targetDir));
|
||||
|
||||
if (is_link($targetDir)) {
|
||||
if (readlink($targetDir) === $originDir) {
|
||||
return;
|
||||
}
|
||||
$this->remove($targetDir);
|
||||
}
|
||||
|
||||
if (!self::box('symlink', $originDir, $targetDir)) {
|
||||
$this->linkException($originDir, $targetDir, 'symbolic');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hard link, or several hard links to a file.
|
||||
*
|
||||
* @param string|string[] $targetFiles The target file(s)
|
||||
*
|
||||
* @throws FileNotFoundException When original file is missing or not a file
|
||||
* @throws IOException When link fails, including if link already exists
|
||||
*/
|
||||
public function hardlink(string $originFile, $targetFiles)
|
||||
{
|
||||
self::assertFunctionExists('link');
|
||||
|
||||
if (!$this->exists($originFile)) {
|
||||
throw new FileNotFoundException(null, 0, null, $originFile);
|
||||
}
|
||||
|
||||
if (!is_file($originFile)) {
|
||||
throw new FileNotFoundException(sprintf('Origin file "%s" is not a file.', $originFile));
|
||||
}
|
||||
|
||||
foreach ($this->toIterable($targetFiles) as $targetFile) {
|
||||
if (is_file($targetFile)) {
|
||||
if (fileinode($originFile) === fileinode($targetFile)) {
|
||||
continue;
|
||||
}
|
||||
$this->remove($targetFile);
|
||||
}
|
||||
|
||||
if (!self::box('link', $originFile, $targetFile)) {
|
||||
$this->linkException($originFile, $targetFile, 'hard');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
|
||||
*/
|
||||
private function linkException(string $origin, string $target, string $linkType)
|
||||
{
|
||||
if (self::$lastError) {
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && str_contains(self::$lastError, 'error code(1314)')) {
|
||||
throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
|
||||
}
|
||||
}
|
||||
throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s": ', $linkType, $origin, $target).self::$lastError, 0, null, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves links in paths.
|
||||
*
|
||||
* With $canonicalize = false (default)
|
||||
* - if $path does not exist or is not a link, returns null
|
||||
* - if $path is a link, returns the next direct target of the link without considering the existence of the target
|
||||
*
|
||||
* With $canonicalize = true
|
||||
* - if $path does not exist, returns null
|
||||
* - if $path exists, returns its absolute fully resolved final version
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function readlink(string $path, bool $canonicalize = false)
|
||||
{
|
||||
if (!$canonicalize && !is_link($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($canonicalize) {
|
||||
if (!$this->exists($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70410) {
|
||||
$path = readlink($path);
|
||||
}
|
||||
|
||||
return realpath($path);
|
||||
}
|
||||
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && \PHP_VERSION_ID < 70400) {
|
||||
return realpath($path);
|
||||
}
|
||||
|
||||
return readlink($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an existing path, convert it to a path relative to a given starting path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function makePathRelative(string $endPath, string $startPath)
|
||||
{
|
||||
if (!$this->isAbsolutePath($startPath)) {
|
||||
throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath));
|
||||
}
|
||||
|
||||
if (!$this->isAbsolutePath($endPath)) {
|
||||
throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath));
|
||||
}
|
||||
|
||||
// Normalize separators on Windows
|
||||
if ('\\' === \DIRECTORY_SEPARATOR) {
|
||||
$endPath = str_replace('\\', '/', $endPath);
|
||||
$startPath = str_replace('\\', '/', $startPath);
|
||||
}
|
||||
|
||||
$splitDriveLetter = function ($path) {
|
||||
return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
|
||||
? [substr($path, 2), strtoupper($path[0])]
|
||||
: [$path, null];
|
||||
};
|
||||
|
||||
$splitPath = function ($path) {
|
||||
$result = [];
|
||||
|
||||
foreach (explode('/', trim($path, '/')) as $segment) {
|
||||
if ('..' === $segment) {
|
||||
array_pop($result);
|
||||
} elseif ('.' !== $segment && '' !== $segment) {
|
||||
$result[] = $segment;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
};
|
||||
|
||||
[$endPath, $endDriveLetter] = $splitDriveLetter($endPath);
|
||||
[$startPath, $startDriveLetter] = $splitDriveLetter($startPath);
|
||||
|
||||
$startPathArr = $splitPath($startPath);
|
||||
$endPathArr = $splitPath($endPath);
|
||||
|
||||
if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) {
|
||||
// End path is on another drive, so no relative path exists
|
||||
return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : '');
|
||||
}
|
||||
|
||||
// Find for which directory the common path stops
|
||||
$index = 0;
|
||||
while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
|
||||
++$index;
|
||||
}
|
||||
|
||||
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
|
||||
if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
|
||||
$depth = 0;
|
||||
} else {
|
||||
$depth = \count($startPathArr) - $index;
|
||||
}
|
||||
|
||||
// Repeated "../" for each level need to reach the common path
|
||||
$traverser = str_repeat('../', $depth);
|
||||
|
||||
$endPathRemainder = implode('/', \array_slice($endPathArr, $index));
|
||||
|
||||
// Construct $endPath from traversing to the common path, then to the remaining $endPath
|
||||
$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
|
||||
|
||||
return '' === $relativePath ? './' : $relativePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors a directory to another.
|
||||
*
|
||||
* Copies files and directories from the origin directory into the target directory. By default:
|
||||
*
|
||||
* - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
|
||||
* - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
|
||||
*
|
||||
* @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created
|
||||
* @param array $options An array of boolean options
|
||||
* Valid options are:
|
||||
* - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
|
||||
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
|
||||
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
|
||||
*
|
||||
* @throws IOException When file type is unknown
|
||||
*/
|
||||
public function mirror(string $originDir, string $targetDir, ?\Traversable $iterator = null, array $options = [])
|
||||
{
|
||||
$targetDir = rtrim($targetDir, '/\\');
|
||||
$originDir = rtrim($originDir, '/\\');
|
||||
$originDirLen = \strlen($originDir);
|
||||
|
||||
if (!$this->exists($originDir)) {
|
||||
throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir);
|
||||
}
|
||||
|
||||
// Iterate in destination folder to remove obsolete entries
|
||||
if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
|
||||
$deleteIterator = $iterator;
|
||||
if (null === $deleteIterator) {
|
||||
$flags = \FilesystemIterator::SKIP_DOTS;
|
||||
$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
|
||||
}
|
||||
$targetDirLen = \strlen($targetDir);
|
||||
foreach ($deleteIterator as $file) {
|
||||
$origin = $originDir.substr($file->getPathname(), $targetDirLen);
|
||||
if (!$this->exists($origin)) {
|
||||
$this->remove($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$copyOnWindows = $options['copy_on_windows'] ?? false;
|
||||
|
||||
if (null === $iterator) {
|
||||
$flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
|
||||
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
|
||||
}
|
||||
|
||||
$this->mkdir($targetDir);
|
||||
$filesCreatedWhileMirroring = [];
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$target = $targetDir.substr($file->getPathname(), $originDirLen);
|
||||
$filesCreatedWhileMirroring[$target] = true;
|
||||
|
||||
if (!$copyOnWindows && is_link($file)) {
|
||||
$this->symlink($file->getLinkTarget(), $target);
|
||||
} elseif (is_dir($file)) {
|
||||
$this->mkdir($target);
|
||||
} elseif (is_file($file)) {
|
||||
$this->copy($file, $target, $options['override'] ?? false);
|
||||
} else {
|
||||
throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the file path is an absolute path.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAbsolutePath(string $file)
|
||||
{
|
||||
return '' !== $file && (strspn($file, '/\\', 0, 1)
|
||||
|| (\strlen($file) > 3 && ctype_alpha($file[0])
|
||||
&& ':' === $file[1]
|
||||
&& strspn($file, '/\\', 2, 1)
|
||||
)
|
||||
|| null !== parse_url($file, \PHP_URL_SCHEME)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a temporary file with support for custom stream wrappers.
|
||||
*
|
||||
* @param string $prefix The prefix of the generated temporary filename
|
||||
* Note: Windows uses only the first three characters of prefix
|
||||
* @param string $suffix The suffix of the generated temporary filename
|
||||
*
|
||||
* @return string The new temporary filename (with path), or throw an exception on failure
|
||||
*/
|
||||
public function tempnam(string $dir, string $prefix/* , string $suffix = '' */)
|
||||
{
|
||||
$suffix = \func_num_args() > 2 ? func_get_arg(2) : '';
|
||||
[$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir);
|
||||
|
||||
// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
|
||||
if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) {
|
||||
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
|
||||
if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) {
|
||||
if (null !== $scheme && 'gs' !== $scheme) {
|
||||
return $scheme.'://'.$tmpFile;
|
||||
}
|
||||
|
||||
return $tmpFile;
|
||||
}
|
||||
|
||||
throw new IOException('A temporary file could not be created: '.self::$lastError);
|
||||
}
|
||||
|
||||
// Loop until we create a valid temp file or have reached 10 attempts
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
// Create a unique filename
|
||||
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix;
|
||||
|
||||
// Use fopen instead of file_exists as some streams do not support stat
|
||||
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
|
||||
if (!$handle = self::box('fopen', $tmpFile, 'x+')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Close the file if it was successfully opened
|
||||
self::box('fclose', $handle);
|
||||
|
||||
return $tmpFile;
|
||||
}
|
||||
|
||||
throw new IOException('A temporary file could not be created: '.self::$lastError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically dumps content into a file.
|
||||
*
|
||||
* @param string|resource $content The data to write into the file
|
||||
*
|
||||
* @throws IOException if the file cannot be written to
|
||||
*/
|
||||
public function dumpFile(string $filename, $content)
|
||||
{
|
||||
if (\is_array($content)) {
|
||||
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
|
||||
}
|
||||
|
||||
$dir = \dirname($filename);
|
||||
|
||||
if (is_link($filename) && $linkTarget = $this->readlink($filename)) {
|
||||
$this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
$this->mkdir($dir);
|
||||
}
|
||||
|
||||
// Will create a temp file with 0600 access rights
|
||||
// when the filesystem supports chmod.
|
||||
$tmpFile = $this->tempnam($dir, basename($filename));
|
||||
|
||||
try {
|
||||
if (false === self::box('file_put_contents', $tmpFile, $content)) {
|
||||
throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename);
|
||||
}
|
||||
|
||||
self::box('chmod', $tmpFile, self::box('fileperms', $filename) ?: 0666 & ~umask());
|
||||
|
||||
$this->rename($tmpFile, $filename, true);
|
||||
} finally {
|
||||
if (file_exists($tmpFile)) {
|
||||
if ('\\' === \DIRECTORY_SEPARATOR && !is_writable($tmpFile)) {
|
||||
self::box('chmod', $tmpFile, self::box('fileperms', $tmpFile) | 0200);
|
||||
}
|
||||
|
||||
self::box('unlink', $tmpFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends content to an existing file.
|
||||
*
|
||||
* @param string|resource $content The content to append
|
||||
* @param bool $lock Whether the file should be locked when writing to it
|
||||
*
|
||||
* @throws IOException If the file is not writable
|
||||
*/
|
||||
public function appendToFile(string $filename, $content/* , bool $lock = false */)
|
||||
{
|
||||
if (\is_array($content)) {
|
||||
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
|
||||
}
|
||||
|
||||
$dir = \dirname($filename);
|
||||
|
||||
if (!is_dir($dir)) {
|
||||
$this->mkdir($dir);
|
||||
}
|
||||
|
||||
$lock = \func_num_args() > 2 && func_get_arg(2);
|
||||
|
||||
if (false === self::box('file_put_contents', $filename, $content, \FILE_APPEND | ($lock ? \LOCK_EX : 0))) {
|
||||
throw new IOException(sprintf('Failed to write file "%s": ', $filename).self::$lastError, 0, null, $filename);
|
||||
}
|
||||
}
|
||||
|
||||
private function toIterable($files): iterable
|
||||
{
|
||||
return is_iterable($files) ? $files : [$files];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
|
||||
*/
|
||||
private function getSchemeAndHierarchy(string $filename): array
|
||||
{
|
||||
$components = explode('://', $filename, 2);
|
||||
|
||||
return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]];
|
||||
}
|
||||
|
||||
private static function assertFunctionExists(string $func): void
|
||||
{
|
||||
if (!\function_exists($func)) {
|
||||
throw new IOException(sprintf('Unable to perform filesystem operation because the "%s()" function has been disabled.', $func));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed ...$args
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private static function box(string $func, ...$args)
|
||||
{
|
||||
self::assertFunctionExists($func);
|
||||
|
||||
self::$lastError = null;
|
||||
set_error_handler(__CLASS__.'::handleError');
|
||||
try {
|
||||
return $func(...$args);
|
||||
} finally {
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function handleError(int $type, string $msg)
|
||||
{
|
||||
self::$lastError = $msg;
|
||||
}
|
||||
}
|
||||
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/LICENSE
vendored
Normal file
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
819
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/Path.php
vendored
Normal file
819
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/Path.php
vendored
Normal file
@@ -0,0 +1,819 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\Filesystem;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\Filesystem\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Contains utility methods for handling path strings.
|
||||
*
|
||||
* The methods in this class are able to deal with both UNIX and Windows paths
|
||||
* with both forward and backward slashes. All methods return normalized parts
|
||||
* containing only forward slashes and no excess "." and ".." segments.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
* @author Thomas Schulz <mail@king2500.net>
|
||||
* @author Théo Fidry <theo.fidry@gmail.com>
|
||||
*/
|
||||
final class Path
|
||||
{
|
||||
/**
|
||||
* The number of buffer entries that triggers a cleanup operation.
|
||||
*/
|
||||
private const CLEANUP_THRESHOLD = 1250;
|
||||
|
||||
/**
|
||||
* The buffer size after the cleanup operation.
|
||||
*/
|
||||
private const CLEANUP_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* Buffers input/output of {@link canonicalize()}.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
private static $buffer = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $bufferSize = 0;
|
||||
|
||||
/**
|
||||
* Canonicalizes the given path.
|
||||
*
|
||||
* During normalization, all slashes are replaced by forward slashes ("/").
|
||||
* Furthermore, all "." and ".." segments are removed as far as possible.
|
||||
* ".." segments at the beginning of relative paths are not removed.
|
||||
*
|
||||
* ```php
|
||||
* echo Path::canonicalize("\symfony\puli\..\css\style.css");
|
||||
* // => /symfony/css/style.css
|
||||
*
|
||||
* echo Path::canonicalize("../css/./style.css");
|
||||
* // => ../css/style.css
|
||||
* ```
|
||||
*
|
||||
* This method is able to deal with both UNIX and Windows paths.
|
||||
*/
|
||||
public static function canonicalize(string $path): string
|
||||
{
|
||||
if ('' === $path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// This method is called by many other methods in this class. Buffer
|
||||
// the canonicalized paths to make up for the severe performance
|
||||
// decrease.
|
||||
if (isset(self::$buffer[$path])) {
|
||||
return self::$buffer[$path];
|
||||
}
|
||||
|
||||
// Replace "~" with user's home directory.
|
||||
if ('~' === $path[0]) {
|
||||
$path = self::getHomeDirectory().substr($path, 1);
|
||||
}
|
||||
|
||||
$path = self::normalize($path);
|
||||
|
||||
[$root, $pathWithoutRoot] = self::split($path);
|
||||
|
||||
$canonicalParts = self::findCanonicalParts($root, $pathWithoutRoot);
|
||||
|
||||
// Add the root directory again
|
||||
self::$buffer[$path] = $canonicalPath = $root.implode('/', $canonicalParts);
|
||||
++self::$bufferSize;
|
||||
|
||||
// Clean up regularly to prevent memory leaks
|
||||
if (self::$bufferSize > self::CLEANUP_THRESHOLD) {
|
||||
self::$buffer = \array_slice(self::$buffer, -self::CLEANUP_SIZE, null, true);
|
||||
self::$bufferSize = self::CLEANUP_SIZE;
|
||||
}
|
||||
|
||||
return $canonicalPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the given path.
|
||||
*
|
||||
* During normalization, all slashes are replaced by forward slashes ("/").
|
||||
* Contrary to {@link canonicalize()}, this method does not remove invalid
|
||||
* or dot path segments. Consequently, it is much more efficient and should
|
||||
* be used whenever the given path is known to be a valid, absolute system
|
||||
* path.
|
||||
*
|
||||
* This method is able to deal with both UNIX and Windows paths.
|
||||
*/
|
||||
public static function normalize(string $path): string
|
||||
{
|
||||
return str_replace('\\', '/', $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the directory part of the path.
|
||||
*
|
||||
* This method is similar to PHP's dirname(), but handles various cases
|
||||
* where dirname() returns a weird result:
|
||||
*
|
||||
* - dirname() does not accept backslashes on UNIX
|
||||
* - dirname("C:/symfony") returns "C:", not "C:/"
|
||||
* - dirname("C:/") returns ".", not "C:/"
|
||||
* - dirname("C:") returns ".", not "C:/"
|
||||
* - dirname("symfony") returns ".", not ""
|
||||
* - dirname() does not canonicalize the result
|
||||
*
|
||||
* This method fixes these shortcomings and behaves like dirname()
|
||||
* otherwise.
|
||||
*
|
||||
* The result is a canonical path.
|
||||
*
|
||||
* @return string The canonical directory part. Returns the root directory
|
||||
* if the root directory is passed. Returns an empty string
|
||||
* if a relative path is passed that contains no slashes.
|
||||
* Returns an empty string if an empty string is passed.
|
||||
*/
|
||||
public static function getDirectory(string $path): string
|
||||
{
|
||||
if ('' === $path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$path = self::canonicalize($path);
|
||||
|
||||
// Maintain scheme
|
||||
if (false !== $schemeSeparatorPosition = strpos($path, '://')) {
|
||||
$scheme = substr($path, 0, $schemeSeparatorPosition + 3);
|
||||
$path = substr($path, $schemeSeparatorPosition + 3);
|
||||
} else {
|
||||
$scheme = '';
|
||||
}
|
||||
|
||||
if (false === $dirSeparatorPosition = strrpos($path, '/')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Directory equals root directory "/"
|
||||
if (0 === $dirSeparatorPosition) {
|
||||
return $scheme.'/';
|
||||
}
|
||||
|
||||
// Directory equals Windows root "C:/"
|
||||
if (2 === $dirSeparatorPosition && ctype_alpha($path[0]) && ':' === $path[1]) {
|
||||
return $scheme.substr($path, 0, 3);
|
||||
}
|
||||
|
||||
return $scheme.substr($path, 0, $dirSeparatorPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns canonical path of the user's home directory.
|
||||
*
|
||||
* Supported operating systems:
|
||||
*
|
||||
* - UNIX
|
||||
* - Windows8 and upper
|
||||
*
|
||||
* If your operating system or environment isn't supported, an exception is thrown.
|
||||
*
|
||||
* The result is a canonical path.
|
||||
*
|
||||
* @throws RuntimeException If your operating system or environment isn't supported
|
||||
*/
|
||||
public static function getHomeDirectory(): string
|
||||
{
|
||||
// For UNIX support
|
||||
if (getenv('HOME')) {
|
||||
return self::canonicalize(getenv('HOME'));
|
||||
}
|
||||
|
||||
// For >= Windows8 support
|
||||
if (getenv('HOMEDRIVE') && getenv('HOMEPATH')) {
|
||||
return self::canonicalize(getenv('HOMEDRIVE').getenv('HOMEPATH'));
|
||||
}
|
||||
|
||||
throw new RuntimeException("Cannot find the home directory path: Your environment or operating system isn't supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root directory of a path.
|
||||
*
|
||||
* The result is a canonical path.
|
||||
*
|
||||
* @return string The canonical root directory. Returns an empty string if
|
||||
* the given path is relative or empty.
|
||||
*/
|
||||
public static function getRoot(string $path): string
|
||||
{
|
||||
if ('' === $path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Maintain scheme
|
||||
if (false !== $schemeSeparatorPosition = strpos($path, '://')) {
|
||||
$scheme = substr($path, 0, $schemeSeparatorPosition + 3);
|
||||
$path = substr($path, $schemeSeparatorPosition + 3);
|
||||
} else {
|
||||
$scheme = '';
|
||||
}
|
||||
|
||||
$firstCharacter = $path[0];
|
||||
|
||||
// UNIX root "/" or "\" (Windows style)
|
||||
if ('/' === $firstCharacter || '\\' === $firstCharacter) {
|
||||
return $scheme.'/';
|
||||
}
|
||||
|
||||
$length = \strlen($path);
|
||||
|
||||
// Windows root
|
||||
if ($length > 1 && ':' === $path[1] && ctype_alpha($firstCharacter)) {
|
||||
// Special case: "C:"
|
||||
if (2 === $length) {
|
||||
return $scheme.$path.'/';
|
||||
}
|
||||
|
||||
// Normal case: "C:/ or "C:\"
|
||||
if ('/' === $path[2] || '\\' === $path[2]) {
|
||||
return $scheme.$firstCharacter.$path[1].'/';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file name without the extension from a file path.
|
||||
*
|
||||
* @param string|null $extension if specified, only that extension is cut
|
||||
* off (may contain leading dot)
|
||||
*/
|
||||
public static function getFilenameWithoutExtension(string $path, ?string $extension = null): string
|
||||
{
|
||||
if ('' === $path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (null !== $extension) {
|
||||
// remove extension and trailing dot
|
||||
return rtrim(basename($path, $extension), '.');
|
||||
}
|
||||
|
||||
return pathinfo($path, \PATHINFO_FILENAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the extension from a file path (without leading dot).
|
||||
*
|
||||
* @param bool $forceLowerCase forces the extension to be lower-case
|
||||
*/
|
||||
public static function getExtension(string $path, bool $forceLowerCase = false): string
|
||||
{
|
||||
if ('' === $path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$extension = pathinfo($path, \PATHINFO_EXTENSION);
|
||||
|
||||
if ($forceLowerCase) {
|
||||
$extension = self::toLower($extension);
|
||||
}
|
||||
|
||||
return $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the path has an (or the specified) extension.
|
||||
*
|
||||
* @param string $path the path string
|
||||
* @param string|string[]|null $extensions if null or not provided, checks if
|
||||
* an extension exists, otherwise
|
||||
* checks for the specified extension
|
||||
* or array of extensions (with or
|
||||
* without leading dot)
|
||||
* @param bool $ignoreCase whether to ignore case-sensitivity
|
||||
*/
|
||||
public static function hasExtension(string $path, $extensions = null, bool $ignoreCase = false): bool
|
||||
{
|
||||
if ('' === $path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actualExtension = self::getExtension($path, $ignoreCase);
|
||||
|
||||
// Only check if path has any extension
|
||||
if ([] === $extensions || null === $extensions) {
|
||||
return '' !== $actualExtension;
|
||||
}
|
||||
|
||||
if (\is_string($extensions)) {
|
||||
$extensions = [$extensions];
|
||||
}
|
||||
|
||||
foreach ($extensions as $key => $extension) {
|
||||
if ($ignoreCase) {
|
||||
$extension = self::toLower($extension);
|
||||
}
|
||||
|
||||
// remove leading '.' in extensions array
|
||||
$extensions[$key] = ltrim($extension, '.');
|
||||
}
|
||||
|
||||
return \in_array($actualExtension, $extensions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the extension of a path string.
|
||||
*
|
||||
* @param string $path The path string with filename.ext to change.
|
||||
* @param string $extension new extension (with or without leading dot)
|
||||
*
|
||||
* @return string the path string with new file extension
|
||||
*/
|
||||
public static function changeExtension(string $path, string $extension): string
|
||||
{
|
||||
if ('' === $path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$actualExtension = self::getExtension($path);
|
||||
$extension = ltrim($extension, '.');
|
||||
|
||||
// No extension for paths
|
||||
if ('/' === substr($path, -1)) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
// No actual extension in path
|
||||
if (empty($actualExtension)) {
|
||||
return $path.('.' === substr($path, -1) ? '' : '.').$extension;
|
||||
}
|
||||
|
||||
return substr($path, 0, -\strlen($actualExtension)).$extension;
|
||||
}
|
||||
|
||||
public static function isAbsolute(string $path): bool
|
||||
{
|
||||
if ('' === $path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Strip scheme
|
||||
if (false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) {
|
||||
$path = substr($path, $schemeSeparatorPosition + 3);
|
||||
}
|
||||
|
||||
$firstCharacter = $path[0];
|
||||
|
||||
// UNIX root "/" or "\" (Windows style)
|
||||
if ('/' === $firstCharacter || '\\' === $firstCharacter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Windows root
|
||||
if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) {
|
||||
// Special case: "C:"
|
||||
if (2 === \strlen($path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Normal case: "C:/ or "C:\"
|
||||
if ('/' === $path[2] || '\\' === $path[2]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isRelative(string $path): bool
|
||||
{
|
||||
return !self::isAbsolute($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a relative path into an absolute path in canonical form.
|
||||
*
|
||||
* Usually, the relative path is appended to the given base path. Dot
|
||||
* segments ("." and "..") are removed/collapsed and all slashes turned
|
||||
* into forward slashes.
|
||||
*
|
||||
* ```php
|
||||
* echo Path::makeAbsolute("../style.css", "/symfony/puli/css");
|
||||
* // => /symfony/puli/style.css
|
||||
* ```
|
||||
*
|
||||
* If an absolute path is passed, that path is returned unless its root
|
||||
* directory is different than the one of the base path. In that case, an
|
||||
* exception is thrown.
|
||||
*
|
||||
* ```php
|
||||
* Path::makeAbsolute("/style.css", "/symfony/puli/css");
|
||||
* // => /style.css
|
||||
*
|
||||
* Path::makeAbsolute("C:/style.css", "C:/symfony/puli/css");
|
||||
* // => C:/style.css
|
||||
*
|
||||
* Path::makeAbsolute("C:/style.css", "/symfony/puli/css");
|
||||
* // InvalidArgumentException
|
||||
* ```
|
||||
*
|
||||
* If the base path is not an absolute path, an exception is thrown.
|
||||
*
|
||||
* The result is a canonical path.
|
||||
*
|
||||
* @param string $basePath an absolute base path
|
||||
*
|
||||
* @throws InvalidArgumentException if the base path is not absolute or if
|
||||
* the given path is an absolute path with
|
||||
* a different root than the base path
|
||||
*/
|
||||
public static function makeAbsolute(string $path, string $basePath): string
|
||||
{
|
||||
if ('' === $basePath) {
|
||||
throw new InvalidArgumentException(sprintf('The base path must be a non-empty string. Got: "%s".', $basePath));
|
||||
}
|
||||
|
||||
if (!self::isAbsolute($basePath)) {
|
||||
throw new InvalidArgumentException(sprintf('The base path "%s" is not an absolute path.', $basePath));
|
||||
}
|
||||
|
||||
if (self::isAbsolute($path)) {
|
||||
return self::canonicalize($path);
|
||||
}
|
||||
|
||||
if (false !== $schemeSeparatorPosition = strpos($basePath, '://')) {
|
||||
$scheme = substr($basePath, 0, $schemeSeparatorPosition + 3);
|
||||
$basePath = substr($basePath, $schemeSeparatorPosition + 3);
|
||||
} else {
|
||||
$scheme = '';
|
||||
}
|
||||
|
||||
return $scheme.self::canonicalize(rtrim($basePath, '/\\').'/'.$path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a path into a relative path.
|
||||
*
|
||||
* The relative path is created relative to the given base path:
|
||||
*
|
||||
* ```php
|
||||
* echo Path::makeRelative("/symfony/style.css", "/symfony/puli");
|
||||
* // => ../style.css
|
||||
* ```
|
||||
*
|
||||
* If a relative path is passed and the base path is absolute, the relative
|
||||
* path is returned unchanged:
|
||||
*
|
||||
* ```php
|
||||
* Path::makeRelative("style.css", "/symfony/puli/css");
|
||||
* // => style.css
|
||||
* ```
|
||||
*
|
||||
* If both paths are relative, the relative path is created with the
|
||||
* assumption that both paths are relative to the same directory:
|
||||
*
|
||||
* ```php
|
||||
* Path::makeRelative("style.css", "symfony/puli/css");
|
||||
* // => ../../../style.css
|
||||
* ```
|
||||
*
|
||||
* If both paths are absolute, their root directory must be the same,
|
||||
* otherwise an exception is thrown:
|
||||
*
|
||||
* ```php
|
||||
* Path::makeRelative("C:/symfony/style.css", "/symfony/puli");
|
||||
* // InvalidArgumentException
|
||||
* ```
|
||||
*
|
||||
* If the passed path is absolute, but the base path is not, an exception
|
||||
* is thrown as well:
|
||||
*
|
||||
* ```php
|
||||
* Path::makeRelative("/symfony/style.css", "symfony/puli");
|
||||
* // InvalidArgumentException
|
||||
* ```
|
||||
*
|
||||
* If the base path is not an absolute path, an exception is thrown.
|
||||
*
|
||||
* The result is a canonical path.
|
||||
*
|
||||
* @throws InvalidArgumentException if the base path is not absolute or if
|
||||
* the given path has a different root
|
||||
* than the base path
|
||||
*/
|
||||
public static function makeRelative(string $path, string $basePath): string
|
||||
{
|
||||
$path = self::canonicalize($path);
|
||||
$basePath = self::canonicalize($basePath);
|
||||
|
||||
[$root, $relativePath] = self::split($path);
|
||||
[$baseRoot, $relativeBasePath] = self::split($basePath);
|
||||
|
||||
// If the base path is given as absolute path and the path is already
|
||||
// relative, consider it to be relative to the given absolute path
|
||||
// already
|
||||
if ('' === $root && '' !== $baseRoot) {
|
||||
// If base path is already in its root
|
||||
if ('' === $relativeBasePath) {
|
||||
$relativePath = ltrim($relativePath, './\\');
|
||||
}
|
||||
|
||||
return $relativePath;
|
||||
}
|
||||
|
||||
// If the passed path is absolute, but the base path is not, we
|
||||
// cannot generate a relative path
|
||||
if ('' !== $root && '' === $baseRoot) {
|
||||
throw new InvalidArgumentException(sprintf('The absolute path "%s" cannot be made relative to the relative path "%s". You should provide an absolute base path instead.', $path, $basePath));
|
||||
}
|
||||
|
||||
// Fail if the roots of the two paths are different
|
||||
if ($baseRoot && $root !== $baseRoot) {
|
||||
throw new InvalidArgumentException(sprintf('The path "%s" cannot be made relative to "%s", because they have different roots ("%s" and "%s").', $path, $basePath, $root, $baseRoot));
|
||||
}
|
||||
|
||||
if ('' === $relativeBasePath) {
|
||||
return $relativePath;
|
||||
}
|
||||
|
||||
// Build a "../../" prefix with as many "../" parts as necessary
|
||||
$parts = explode('/', $relativePath);
|
||||
$baseParts = explode('/', $relativeBasePath);
|
||||
$dotDotPrefix = '';
|
||||
|
||||
// Once we found a non-matching part in the prefix, we need to add
|
||||
// "../" parts for all remaining parts
|
||||
$match = true;
|
||||
|
||||
foreach ($baseParts as $index => $basePart) {
|
||||
if ($match && isset($parts[$index]) && $basePart === $parts[$index]) {
|
||||
unset($parts[$index]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$match = false;
|
||||
$dotDotPrefix .= '../';
|
||||
}
|
||||
|
||||
return rtrim($dotDotPrefix.implode('/', $parts), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given path is on the local filesystem.
|
||||
*/
|
||||
public static function isLocal(string $path): bool
|
||||
{
|
||||
return '' !== $path && false === strpos($path, '://');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the longest common base path in canonical form of a set of paths or
|
||||
* `null` if the paths are on different Windows partitions.
|
||||
*
|
||||
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
|
||||
* into forward slashes.
|
||||
*
|
||||
* ```php
|
||||
* $basePath = Path::getLongestCommonBasePath(
|
||||
* '/symfony/css/style.css',
|
||||
* '/symfony/css/..'
|
||||
* );
|
||||
* // => /symfony
|
||||
* ```
|
||||
*
|
||||
* The root is returned if no common base path can be found:
|
||||
*
|
||||
* ```php
|
||||
* $basePath = Path::getLongestCommonBasePath(
|
||||
* '/symfony/css/style.css',
|
||||
* '/puli/css/..'
|
||||
* );
|
||||
* // => /
|
||||
* ```
|
||||
*
|
||||
* If the paths are located on different Windows partitions, `null` is
|
||||
* returned.
|
||||
*
|
||||
* ```php
|
||||
* $basePath = Path::getLongestCommonBasePath(
|
||||
* 'C:/symfony/css/style.css',
|
||||
* 'D:/symfony/css/..'
|
||||
* );
|
||||
* // => null
|
||||
* ```
|
||||
*/
|
||||
public static function getLongestCommonBasePath(string ...$paths): ?string
|
||||
{
|
||||
[$bpRoot, $basePath] = self::split(self::canonicalize(reset($paths)));
|
||||
|
||||
for (next($paths); null !== key($paths) && '' !== $basePath; next($paths)) {
|
||||
[$root, $path] = self::split(self::canonicalize(current($paths)));
|
||||
|
||||
// If we deal with different roots (e.g. C:/ vs. D:/), it's time
|
||||
// to quit
|
||||
if ($root !== $bpRoot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make the base path shorter until it fits into path
|
||||
while (true) {
|
||||
if ('.' === $basePath) {
|
||||
// No more base paths
|
||||
$basePath = '';
|
||||
|
||||
// next path
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// Prevent false positives for common prefixes
|
||||
// see isBasePath()
|
||||
if (0 === strpos($path.'/', $basePath.'/')) {
|
||||
// next path
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$basePath = \dirname($basePath);
|
||||
}
|
||||
}
|
||||
|
||||
return $bpRoot.$basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins two or more path strings into a canonical path.
|
||||
*/
|
||||
public static function join(string ...$paths): string
|
||||
{
|
||||
$finalPath = null;
|
||||
$wasScheme = false;
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if ('' === $path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $finalPath) {
|
||||
// For first part we keep slashes, like '/top', 'C:\' or 'phar://'
|
||||
$finalPath = $path;
|
||||
$wasScheme = (false !== strpos($path, '://'));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only add slash if previous part didn't end with '/' or '\'
|
||||
if (!\in_array(substr($finalPath, -1), ['/', '\\'])) {
|
||||
$finalPath .= '/';
|
||||
}
|
||||
|
||||
// If first part included a scheme like 'phar://' we allow \current part to start with '/', otherwise trim
|
||||
$finalPath .= $wasScheme ? $path : ltrim($path, '/');
|
||||
$wasScheme = false;
|
||||
}
|
||||
|
||||
if (null === $finalPath) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::canonicalize($finalPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a path is a base path of another path.
|
||||
*
|
||||
* Dot segments ("." and "..") are removed/collapsed and all slashes turned
|
||||
* into forward slashes.
|
||||
*
|
||||
* ```php
|
||||
* Path::isBasePath('/symfony', '/symfony/css');
|
||||
* // => true
|
||||
*
|
||||
* Path::isBasePath('/symfony', '/symfony');
|
||||
* // => true
|
||||
*
|
||||
* Path::isBasePath('/symfony', '/symfony/..');
|
||||
* // => false
|
||||
*
|
||||
* Path::isBasePath('/symfony', '/puli');
|
||||
* // => false
|
||||
* ```
|
||||
*/
|
||||
public static function isBasePath(string $basePath, string $ofPath): bool
|
||||
{
|
||||
$basePath = self::canonicalize($basePath);
|
||||
$ofPath = self::canonicalize($ofPath);
|
||||
|
||||
// Append slashes to prevent false positives when two paths have
|
||||
// a common prefix, for example /base/foo and /base/foobar.
|
||||
// Don't append a slash for the root "/", because then that root
|
||||
// won't be discovered as common prefix ("//" is not a prefix of
|
||||
// "/foobar/").
|
||||
return 0 === strpos($ofPath.'/', rtrim($basePath, '/').'/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function findCanonicalParts(string $root, string $pathWithoutRoot): array
|
||||
{
|
||||
$parts = explode('/', $pathWithoutRoot);
|
||||
|
||||
$canonicalParts = [];
|
||||
|
||||
// Collapse "." and "..", if possible
|
||||
foreach ($parts as $part) {
|
||||
if ('.' === $part || '' === $part) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collapse ".." with the previous part, if one exists
|
||||
// Don't collapse ".." if the previous part is also ".."
|
||||
if ('..' === $part && \count($canonicalParts) > 0 && '..' !== $canonicalParts[\count($canonicalParts) - 1]) {
|
||||
array_pop($canonicalParts);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only add ".." prefixes for relative paths
|
||||
if ('..' !== $part || '' === $root) {
|
||||
$canonicalParts[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
return $canonicalParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a canonical path into its root directory and the remainder.
|
||||
*
|
||||
* If the path has no root directory, an empty root directory will be
|
||||
* returned.
|
||||
*
|
||||
* If the root directory is a Windows style partition, the resulting root
|
||||
* will always contain a trailing slash.
|
||||
*
|
||||
* list ($root, $path) = Path::split("C:/symfony")
|
||||
* // => ["C:/", "symfony"]
|
||||
*
|
||||
* list ($root, $path) = Path::split("C:")
|
||||
* // => ["C:/", ""]
|
||||
*
|
||||
* @return array{string, string} an array with the root directory and the remaining relative path
|
||||
*/
|
||||
private static function split(string $path): array
|
||||
{
|
||||
if ('' === $path) {
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
// Remember scheme as part of the root, if any
|
||||
if (false !== $schemeSeparatorPosition = strpos($path, '://')) {
|
||||
$root = substr($path, 0, $schemeSeparatorPosition + 3);
|
||||
$path = substr($path, $schemeSeparatorPosition + 3);
|
||||
} else {
|
||||
$root = '';
|
||||
}
|
||||
|
||||
$length = \strlen($path);
|
||||
|
||||
// Remove and remember root directory
|
||||
if (0 === strpos($path, '/')) {
|
||||
$root .= '/';
|
||||
$path = $length > 1 ? substr($path, 1) : '';
|
||||
} elseif ($length > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
|
||||
if (2 === $length) {
|
||||
// Windows special case: "C:"
|
||||
$root .= $path.'/';
|
||||
$path = '';
|
||||
} elseif ('/' === $path[2]) {
|
||||
// Windows normal case: "C:/"..
|
||||
$root .= substr($path, 0, 3);
|
||||
$path = $length > 3 ? substr($path, 3) : '';
|
||||
}
|
||||
}
|
||||
|
||||
return [$root, $path];
|
||||
}
|
||||
|
||||
private static function toLower(string $string): string
|
||||
{
|
||||
if (false !== $encoding = mb_detect_encoding($string, null, true)) {
|
||||
return mb_strtolower($string, $encoding);
|
||||
}
|
||||
|
||||
return strtolower($string);
|
||||
}
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
13
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/README.md
vendored
Normal file
13
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/filesystem/README.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
Filesystem Component
|
||||
====================
|
||||
|
||||
The Filesystem component provides basic utilities for the filesystem.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/filesystem.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
@@ -0,0 +1,5 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
The changelog is maintained for all Symfony contracts at the following URL:
|
||||
https://github.com/symfony/contracts/blob/main/CHANGELOG.md
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* The interface of chunks returned by ResponseStreamInterface::current().
|
||||
*
|
||||
* When the chunk is first, last or timeout, the content MUST be empty.
|
||||
* When an unchecked timeout or a network error occurs, a TransportExceptionInterface
|
||||
* MUST be thrown by the destructor unless one was already thrown by another method.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface ChunkInterface
|
||||
{
|
||||
/**
|
||||
* Tells when the idle timeout has been reached.
|
||||
*
|
||||
* @throws TransportExceptionInterface on a network error
|
||||
*/
|
||||
public function isTimeout(): bool;
|
||||
|
||||
/**
|
||||
* Tells when headers just arrived.
|
||||
*
|
||||
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
|
||||
*/
|
||||
public function isFirst(): bool;
|
||||
|
||||
/**
|
||||
* Tells when the body just completed.
|
||||
*
|
||||
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
|
||||
*/
|
||||
public function isLast(): bool;
|
||||
|
||||
/**
|
||||
* Returns a [status code, headers] tuple when a 1xx status code was just received.
|
||||
*
|
||||
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
|
||||
*/
|
||||
public function getInformationalStatus(): ?array;
|
||||
|
||||
/**
|
||||
* Returns the content of the response chunk.
|
||||
*
|
||||
* @throws TransportExceptionInterface on a network error or when the idle timeout is reached
|
||||
*/
|
||||
public function getContent(): string;
|
||||
|
||||
/**
|
||||
* Returns the offset of the chunk in the response body.
|
||||
*/
|
||||
public function getOffset(): int;
|
||||
|
||||
/**
|
||||
* In case of error, returns the message that describes it.
|
||||
*/
|
||||
public function getError(): ?string;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
/**
|
||||
* When a 4xx response is returned.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface ClientExceptionInterface extends HttpExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
/**
|
||||
* When a content-type cannot be decoded to the expected representation.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface DecodingExceptionInterface extends ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
/**
|
||||
* The base interface for all exceptions in the contract.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Base interface for HTTP-related exceptions.
|
||||
*
|
||||
* @author Anton Chernikov <anton_ch1989@mail.ru>
|
||||
*/
|
||||
interface HttpExceptionInterface extends ExceptionInterface
|
||||
{
|
||||
public function getResponse(): ResponseInterface;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
/**
|
||||
* When a 3xx response is returned and the "max_redirects" option has been reached.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface RedirectionExceptionInterface extends HttpExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
/**
|
||||
* When a 5xx response is returned.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface ServerExceptionInterface extends HttpExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
/**
|
||||
* When an idle timeout occurs.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface TimeoutExceptionInterface extends TransportExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception;
|
||||
|
||||
/**
|
||||
* When any error happens at the transport level.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface TransportExceptionInterface extends ExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Test\HttpClientTestCase;
|
||||
|
||||
/**
|
||||
* Provides flexible methods for requesting HTTP resources synchronously or asynchronously.
|
||||
*
|
||||
* @see HttpClientTestCase for a reference test suite
|
||||
*
|
||||
* @method static withOptions(array $options) Returns a new instance of the client with new default options
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface HttpClientInterface
|
||||
{
|
||||
public const OPTIONS_DEFAULTS = [
|
||||
'auth_basic' => null, // array|string - an array containing the username as first value, and optionally the
|
||||
// password as the second one; or string like username:password - enabling HTTP Basic
|
||||
// authentication (RFC 7617)
|
||||
'auth_bearer' => null, // string - a token enabling HTTP Bearer authorization (RFC 6750)
|
||||
'query' => [], // string[] - associative array of query string values to merge with the request's URL
|
||||
'headers' => [], // iterable|string[]|string[][] - headers names provided as keys or as part of values
|
||||
'body' => '', // array|string|resource|\Traversable|\Closure - the callback SHOULD yield a string
|
||||
// smaller than the amount requested as argument; the empty string signals EOF; if
|
||||
// an array is passed, it is meant as a form payload of field names and values
|
||||
'json' => null, // mixed - if set, implementations MUST set the "body" option to the JSON-encoded
|
||||
// value and set the "content-type" header to a JSON-compatible value if it is not
|
||||
// explicitly defined in the headers option - typically "application/json"
|
||||
'user_data' => null, // mixed - any extra data to attach to the request (scalar, callable, object...) that
|
||||
// MUST be available via $response->getInfo('user_data') - not used internally
|
||||
'max_redirects' => 20, // int - the maximum number of redirects to follow; a value lower than or equal to 0
|
||||
// means redirects should not be followed; "Authorization" and "Cookie" headers MUST
|
||||
// NOT follow except for the initial host name
|
||||
'http_version' => null, // string - defaults to the best supported version, typically 1.1 or 2.0
|
||||
'base_uri' => null, // string - the URI to resolve relative URLs, following rules in RFC 3986, section 2
|
||||
'buffer' => true, // bool|resource|\Closure - whether the content of the response should be buffered or not,
|
||||
// or a stream resource where the response body should be written,
|
||||
// or a closure telling if/where the response should be buffered based on its headers
|
||||
'on_progress' => null, // callable(int $dlNow, int $dlSize, array $info) - throwing any exceptions MUST abort the
|
||||
// request; it MUST be called on connection, on headers and on completion; it SHOULD be
|
||||
// called on upload/download of data and at least 1/s
|
||||
'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution
|
||||
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
|
||||
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
|
||||
'timeout' => null, // float - the idle timeout (in seconds) - defaults to ini_get('default_socket_timeout')
|
||||
'max_duration' => 0, // float - the maximum execution time (in seconds) for the request+response as a whole;
|
||||
// a value lower than or equal to 0 means it is unlimited
|
||||
'bindto' => '0', // string - the interface or the local socket to bind to
|
||||
'verify_peer' => true, // see https://php.net/context.ssl for the following options
|
||||
'verify_host' => true,
|
||||
'cafile' => null,
|
||||
'capath' => null,
|
||||
'local_cert' => null,
|
||||
'local_pk' => null,
|
||||
'passphrase' => null,
|
||||
'ciphers' => null,
|
||||
'peer_fingerprint' => null,
|
||||
'capture_peer_cert_chain' => false,
|
||||
'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options
|
||||
];
|
||||
|
||||
/**
|
||||
* Requests an HTTP resource.
|
||||
*
|
||||
* Responses MUST be lazy, but their status code MUST be
|
||||
* checked even if none of their public methods are called.
|
||||
*
|
||||
* Implementations are not required to support all options described above; they can also
|
||||
* support more custom options; but in any case, they MUST throw a TransportExceptionInterface
|
||||
* when an unsupported option is passed.
|
||||
*
|
||||
* @throws TransportExceptionInterface When an unsupported option is passed
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* Yields responses chunk by chunk as they complete.
|
||||
*
|
||||
* @param ResponseInterface|iterable<array-key, ResponseInterface> $responses One or more responses created by the current HTTP client
|
||||
* @param float|null $timeout The idle timeout before yielding timeout chunks
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface;
|
||||
}
|
||||
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client-contracts/LICENSE
vendored
Normal file
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client-contracts/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,9 @@
|
||||
Symfony HttpClient Contracts
|
||||
============================
|
||||
|
||||
A set of abstractions extracted out of the Symfony components.
|
||||
|
||||
Can be used to build on semantics that the Symfony components proved useful - and
|
||||
that already have battle tested implementations.
|
||||
|
||||
See https://github.com/symfony/contracts/blob/main/README.md for more information.
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* A (lazily retrieved) HTTP response.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface ResponseInterface
|
||||
{
|
||||
/**
|
||||
* Gets the HTTP status code of the response.
|
||||
*
|
||||
* @throws TransportExceptionInterface when a network error occurs
|
||||
*/
|
||||
public function getStatusCode(): int;
|
||||
|
||||
/**
|
||||
* Gets the HTTP headers of the response.
|
||||
*
|
||||
* @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes
|
||||
*
|
||||
* @return string[][] The headers of the response keyed by header names in lowercase
|
||||
*
|
||||
* @throws TransportExceptionInterface When a network error occurs
|
||||
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
|
||||
* @throws ClientExceptionInterface On a 4xx when $throw is true
|
||||
* @throws ServerExceptionInterface On a 5xx when $throw is true
|
||||
*/
|
||||
public function getHeaders(bool $throw = true): array;
|
||||
|
||||
/**
|
||||
* Gets the response body as a string.
|
||||
*
|
||||
* @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes
|
||||
*
|
||||
* @throws TransportExceptionInterface When a network error occurs
|
||||
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
|
||||
* @throws ClientExceptionInterface On a 4xx when $throw is true
|
||||
* @throws ServerExceptionInterface On a 5xx when $throw is true
|
||||
*/
|
||||
public function getContent(bool $throw = true): string;
|
||||
|
||||
/**
|
||||
* Gets the response body decoded as array, typically from a JSON payload.
|
||||
*
|
||||
* @param bool $throw Whether an exception should be thrown on 3/4/5xx status codes
|
||||
*
|
||||
* @throws DecodingExceptionInterface When the body cannot be decoded to an array
|
||||
* @throws TransportExceptionInterface When a network error occurs
|
||||
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
|
||||
* @throws ClientExceptionInterface On a 4xx when $throw is true
|
||||
* @throws ServerExceptionInterface On a 5xx when $throw is true
|
||||
*/
|
||||
public function toArray(bool $throw = true): array;
|
||||
|
||||
/**
|
||||
* Closes the response stream and all related buffers.
|
||||
*
|
||||
* No further chunk will be yielded after this method has been called.
|
||||
*/
|
||||
public function cancel(): void;
|
||||
|
||||
/**
|
||||
* Returns info coming from the transport layer.
|
||||
*
|
||||
* This method SHOULD NOT throw any ExceptionInterface and SHOULD be non-blocking.
|
||||
* The returned info is "live": it can be empty and can change from one call to
|
||||
* another, as the request/response progresses.
|
||||
*
|
||||
* The following info MUST be returned:
|
||||
* - canceled (bool) - true if the response was canceled using ResponseInterface::cancel(), false otherwise
|
||||
* - error (string|null) - the error message when the transfer was aborted, null otherwise
|
||||
* - http_code (int) - the last response code or 0 when it is not known yet
|
||||
* - http_method (string) - the HTTP verb of the last request
|
||||
* - redirect_count (int) - the number of redirects followed while executing the request
|
||||
* - redirect_url (string|null) - the resolved location of redirect responses, null otherwise
|
||||
* - response_headers (array) - an array modelled after the special $http_response_header variable
|
||||
* - start_time (float) - the time when the request was sent or 0.0 when it's pending
|
||||
* - url (string) - the last effective URL of the request
|
||||
* - user_data (mixed) - the value of the "user_data" request option, null if not set
|
||||
*
|
||||
* When the "capture_peer_cert_chain" option is true, the "peer_certificate_chain"
|
||||
* attribute SHOULD list the peer certificates as an array of OpenSSL X.509 resources.
|
||||
*
|
||||
* Other info SHOULD be named after curl_getinfo()'s associative return value.
|
||||
*
|
||||
* @return mixed An array of all available info, or one of them when $type is
|
||||
* provided, or null when an unsupported type is requested
|
||||
*/
|
||||
public function getInfo(?string $type = null);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient;
|
||||
|
||||
/**
|
||||
* Yields response chunks, returned by HttpClientInterface::stream().
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @extends \Iterator<ResponseInterface, ChunkInterface>
|
||||
*/
|
||||
interface ResponseStreamInterface extends \Iterator
|
||||
{
|
||||
public function key(): ResponseInterface;
|
||||
|
||||
public function current(): ChunkInterface;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
if ('cli-server' !== \PHP_SAPI) {
|
||||
// safe guard against unwanted execution
|
||||
throw new \Exception("You cannot run this script directly, it's a fixture for TestHttpServer.");
|
||||
}
|
||||
|
||||
$vars = [];
|
||||
|
||||
if (!$_POST) {
|
||||
$_POST = json_decode(file_get_contents('php://input'), true);
|
||||
$_POST['content-type'] = $_SERVER['HTTP_CONTENT_TYPE'] ?? '?';
|
||||
}
|
||||
|
||||
$headers = [
|
||||
'SERVER_PROTOCOL',
|
||||
'SERVER_NAME',
|
||||
'REQUEST_URI',
|
||||
'REQUEST_METHOD',
|
||||
'PHP_AUTH_USER',
|
||||
'PHP_AUTH_PW',
|
||||
'REMOTE_ADDR',
|
||||
'REMOTE_PORT',
|
||||
];
|
||||
|
||||
foreach ($headers as $k) {
|
||||
if (isset($_SERVER[$k])) {
|
||||
$vars[$k] = $_SERVER[$k];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($_SERVER as $k => $v) {
|
||||
if (0 === strpos($k, 'HTTP_')) {
|
||||
$vars[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
$json = json_encode($vars, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
|
||||
|
||||
switch (parse_url($vars['REQUEST_URI'], \PHP_URL_PATH)) {
|
||||
default:
|
||||
exit;
|
||||
|
||||
case '/head':
|
||||
header('Content-Length: '.strlen($json), true);
|
||||
break;
|
||||
|
||||
case '/':
|
||||
case '/?a=a&b=b':
|
||||
case 'http://127.0.0.1:8057/':
|
||||
case 'http://localhost:8057/':
|
||||
ob_start('ob_gzhandler');
|
||||
break;
|
||||
|
||||
case '/103':
|
||||
header('HTTP/1.1 103 Early Hints');
|
||||
header('Link: </style.css>; rel=preload; as=style', false);
|
||||
header('Link: </script.js>; rel=preload; as=script', false);
|
||||
flush();
|
||||
usleep(1000);
|
||||
echo "HTTP/1.1 200 OK\r\n";
|
||||
echo "Date: Fri, 26 May 2017 10:02:11 GMT\r\n";
|
||||
echo "Content-Length: 13\r\n";
|
||||
echo "\r\n";
|
||||
echo 'Here the body';
|
||||
exit;
|
||||
|
||||
case '/404':
|
||||
header('Content-Type: application/json', true, 404);
|
||||
break;
|
||||
|
||||
case '/404-gzipped':
|
||||
header('Content-Type: text/plain', true, 404);
|
||||
ob_start('ob_gzhandler');
|
||||
@ob_flush();
|
||||
flush();
|
||||
usleep(300000);
|
||||
echo 'some text';
|
||||
exit;
|
||||
|
||||
case '/301':
|
||||
if ('Basic Zm9vOmJhcg==' === $vars['HTTP_AUTHORIZATION']) {
|
||||
header('Location: http://127.0.0.1:8057/302', true, 301);
|
||||
}
|
||||
break;
|
||||
|
||||
case '/301/bad-tld':
|
||||
header('Location: http://foo.example.', true, 301);
|
||||
break;
|
||||
|
||||
case '/301/invalid':
|
||||
header('Location: //?foo=bar', true, 301);
|
||||
break;
|
||||
|
||||
case '/301/proxy':
|
||||
case 'http://localhost:8057/301/proxy':
|
||||
case 'http://127.0.0.1:8057/301/proxy':
|
||||
header('Location: http://localhost:8057/', true, 301);
|
||||
break;
|
||||
|
||||
case '/302':
|
||||
if (!isset($vars['HTTP_AUTHORIZATION'])) {
|
||||
$location = $_GET['location'] ?? 'http://localhost:8057/';
|
||||
header('Location: '.$location, true, 302);
|
||||
}
|
||||
break;
|
||||
|
||||
case '/302/relative':
|
||||
header('Location: ..', true, 302);
|
||||
break;
|
||||
|
||||
case '/304':
|
||||
header('Content-Length: 10', true, 304);
|
||||
echo '12345';
|
||||
|
||||
return;
|
||||
|
||||
case '/307':
|
||||
header('Location: http://localhost:8057/post', true, 307);
|
||||
break;
|
||||
|
||||
case '/length-broken':
|
||||
header('Content-Length: 1000');
|
||||
break;
|
||||
|
||||
case '/post':
|
||||
$output = json_encode($_POST + ['REQUEST_METHOD' => $vars['REQUEST_METHOD']], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE);
|
||||
header('Content-Type: application/json', true);
|
||||
header('Content-Length: '.strlen($output));
|
||||
echo $output;
|
||||
exit;
|
||||
|
||||
case '/timeout-header':
|
||||
usleep(300000);
|
||||
break;
|
||||
|
||||
case '/timeout-body':
|
||||
echo '<1>';
|
||||
@ob_flush();
|
||||
flush();
|
||||
usleep(500000);
|
||||
echo '<2>';
|
||||
exit;
|
||||
|
||||
case '/timeout-long':
|
||||
ignore_user_abort(false);
|
||||
sleep(1);
|
||||
while (true) {
|
||||
echo '<1>';
|
||||
@ob_flush();
|
||||
flush();
|
||||
usleep(500);
|
||||
}
|
||||
exit;
|
||||
|
||||
case '/chunked':
|
||||
header('Transfer-Encoding: chunked');
|
||||
echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\nesome!\r\n0\r\n\r\n";
|
||||
exit;
|
||||
|
||||
case '/chunked-broken':
|
||||
header('Transfer-Encoding: chunked');
|
||||
echo "8\r\nSymfony \r\n5\r\nis aw\r\n6\r\ne";
|
||||
exit;
|
||||
|
||||
case '/gzip-broken':
|
||||
header('Content-Encoding: gzip');
|
||||
echo str_repeat('-', 1000);
|
||||
exit;
|
||||
|
||||
case '/max-duration':
|
||||
ignore_user_abort(false);
|
||||
while (true) {
|
||||
echo '<1>';
|
||||
@ob_flush();
|
||||
flush();
|
||||
usleep(500);
|
||||
}
|
||||
exit;
|
||||
|
||||
case '/json':
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'documents' => [
|
||||
['id' => '/json/1'],
|
||||
['id' => '/json/2'],
|
||||
['id' => '/json/3'],
|
||||
],
|
||||
]);
|
||||
exit;
|
||||
|
||||
case '/json/1':
|
||||
case '/json/2':
|
||||
case '/json/3':
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode([
|
||||
'title' => $vars['REQUEST_URI'],
|
||||
]);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Content-Type: application/json', true);
|
||||
|
||||
echo $json;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Test;
|
||||
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
class TestHttpServer
|
||||
{
|
||||
private static $process = [];
|
||||
|
||||
/**
|
||||
* @return Process
|
||||
*/
|
||||
public static function start(int $port = 8057)
|
||||
{
|
||||
if (0 > $port) {
|
||||
$port = -$port;
|
||||
$ip = '[::1]';
|
||||
} else {
|
||||
$ip = '127.0.0.1';
|
||||
}
|
||||
|
||||
if (isset(self::$process[$port])) {
|
||||
self::$process[$port]->stop();
|
||||
} else {
|
||||
register_shutdown_function(static function () use ($port) {
|
||||
self::$process[$port]->stop();
|
||||
});
|
||||
}
|
||||
|
||||
$finder = new PhpExecutableFinder();
|
||||
$process = new Process(array_merge([$finder->find(false)], $finder->findArguments(), ['-dopcache.enable=0', '-dvariables_order=EGPCS', '-S', $ip.':'.$port]));
|
||||
$process->setWorkingDirectory(__DIR__.'/Fixtures/web');
|
||||
$process->start();
|
||||
self::$process[$port] = $process;
|
||||
|
||||
do {
|
||||
usleep(50000);
|
||||
} while (!@fopen('http://'.$ip.':'.$port, 'r'));
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
||||
public static function stop(int $port = 8057)
|
||||
{
|
||||
if (isset(self::$process[$port])) {
|
||||
self::$process[$port]->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
182
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/AmpHttpClient.php
vendored
Normal file
182
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/AmpHttpClient.php
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use Amp\CancelledException;
|
||||
use Amp\Http\Client\DelegateHttpClient;
|
||||
use Amp\Http\Client\InterceptedHttpClient;
|
||||
use Amp\Http\Client\PooledHttpClient;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Tunnel\Http1TunnelConnector;
|
||||
use Amp\Promise;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareTrait;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\AmpClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AmpResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
if (!interface_exists(DelegateHttpClient::class)) {
|
||||
throw new \LogicException('You cannot use "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\AmpHttpClient" as the "amphp/http-client" package is not installed. Try running "composer require amphp/http-client:^4.2.1".');
|
||||
}
|
||||
|
||||
if (!interface_exists(Promise::class)) {
|
||||
throw new \LogicException('You cannot use "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\AmpHttpClient" as the installed "amphp/http-client" is not compatible with this version of "symfony/http-client". Try downgrading "amphp/http-client" to "^4.2.1".');
|
||||
}
|
||||
|
||||
/**
|
||||
* A portable implementation of the HttpClientInterface contracts based on Amp's HTTP client.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
private static $emptyDefaults = self::OPTIONS_DEFAULTS;
|
||||
|
||||
/** @var AmpClientState */
|
||||
private $multi;
|
||||
|
||||
/**
|
||||
* @param array $defaultOptions Default requests' options
|
||||
* @param callable|null $clientConfigurator A callable that builds a {@see DelegateHttpClient} from a {@see PooledHttpClient};
|
||||
* passing null builds an {@see InterceptedHttpClient} with 2 retries on failures
|
||||
* @param int $maxHostConnections The maximum number of connections to a single host
|
||||
* @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue
|
||||
*
|
||||
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
|
||||
*/
|
||||
public function __construct(array $defaultOptions = [], ?callable $clientConfigurator = null, int $maxHostConnections = 6, int $maxPendingPushes = 50)
|
||||
{
|
||||
$this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']);
|
||||
|
||||
if ($defaultOptions) {
|
||||
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
|
||||
}
|
||||
|
||||
$this->multi = new AmpClientState($clientConfigurator, $maxHostConnections, $maxPendingPushes, $this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
[$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions);
|
||||
|
||||
$options['proxy'] = self::getProxy($options['proxy'], $url, $options['no_proxy']);
|
||||
|
||||
if (null !== $options['proxy'] && !class_exists(Http1TunnelConnector::class)) {
|
||||
throw new \LogicException('You cannot use the "proxy" option as the "amphp/http-tunnel" package is not installed. Try running "composer require amphp/http-tunnel".');
|
||||
}
|
||||
|
||||
if ($options['bindto']) {
|
||||
if (0 === strpos($options['bindto'], 'if!')) {
|
||||
throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.');
|
||||
}
|
||||
if (0 === strpos($options['bindto'], 'host!')) {
|
||||
$options['bindto'] = substr($options['bindto'], 5);
|
||||
}
|
||||
}
|
||||
|
||||
if (('' !== $options['body'] || 'POST' === $method || isset($options['normalized_headers']['content-length'])) && !isset($options['normalized_headers']['content-type'])) {
|
||||
$options['headers'][] = 'Content-Type: application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
if (!isset($options['normalized_headers']['user-agent'])) {
|
||||
$options['headers'][] = 'User-Agent: Symfony HttpClient/Amp';
|
||||
}
|
||||
|
||||
if (0 < $options['max_duration']) {
|
||||
$options['timeout'] = min($options['max_duration'], $options['timeout']);
|
||||
}
|
||||
|
||||
if ($options['resolve']) {
|
||||
$this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache;
|
||||
}
|
||||
|
||||
if ($options['peer_fingerprint'] && !isset($options['peer_fingerprint']['pin-sha256'])) {
|
||||
throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.');
|
||||
}
|
||||
|
||||
$request = new Request(implode('', $url), $method);
|
||||
$request->setBodySizeLimit(0);
|
||||
|
||||
if ($options['http_version']) {
|
||||
switch ((float) $options['http_version']) {
|
||||
case 1.0: $request->setProtocolVersions(['1.0']); break;
|
||||
case 1.1: $request->setProtocolVersions(['1.1', '1.0']); break;
|
||||
default: $request->setProtocolVersions(['2', '1.1', '1.0']); break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($options['headers'] as $v) {
|
||||
$h = explode(': ', $v, 2);
|
||||
$request->addHeader($h[0], $h[1]);
|
||||
}
|
||||
|
||||
$request->setTcpConnectTimeout(1000 * $options['timeout']);
|
||||
$request->setTlsHandshakeTimeout(1000 * $options['timeout']);
|
||||
$request->setTransferTimeout(1000 * $options['max_duration']);
|
||||
if (method_exists($request, 'setInactivityTimeout')) {
|
||||
$request->setInactivityTimeout(0);
|
||||
}
|
||||
|
||||
if ('' !== $request->getUri()->getUserInfo() && !$request->hasHeader('authorization')) {
|
||||
$auth = explode(':', $request->getUri()->getUserInfo(), 2);
|
||||
$auth = array_map('rawurldecode', $auth) + [1 => ''];
|
||||
$request->setHeader('Authorization', 'Basic '.base64_encode(implode(':', $auth)));
|
||||
}
|
||||
|
||||
return new AmpResponse($this->multi, $request, $options, $this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
if ($responses instanceof AmpResponse) {
|
||||
$responses = [$responses];
|
||||
} elseif (!is_iterable($responses)) {
|
||||
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AmpResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
|
||||
}
|
||||
|
||||
return new ResponseStream(AmpResponse::stream($responses, $timeout));
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->multi->dnsCache = [];
|
||||
|
||||
foreach ($this->multi->pushedResponses as $authority => $pushedResponses) {
|
||||
foreach ($pushedResponses as [$pushedUrl, $pushDeferred]) {
|
||||
$pushDeferred->fail(new CancelledException());
|
||||
|
||||
if ($this->logger) {
|
||||
$this->logger->debug(sprintf('Unused pushed response: "%s"', $pushedUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->multi->pushedResponses = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
|
||||
/**
|
||||
* Eases with processing responses while streaming them.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
trait AsyncDecoratorTrait
|
||||
{
|
||||
use DecoratorTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return AsyncResponse
|
||||
*/
|
||||
abstract public function request(string $method, string $url, array $options = []): ResponseInterface;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
if ($responses instanceof AsyncResponse) {
|
||||
$responses = [$responses];
|
||||
} elseif (!is_iterable($responses)) {
|
||||
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
|
||||
}
|
||||
|
||||
return new ResponseStream(AsyncResponse::stream($responses, $timeout, static::class));
|
||||
}
|
||||
}
|
||||
54
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/CHANGELOG.md
vendored
Normal file
54
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Add `MockHttpClient::setResponseFactory()` method to be able to set response factory after client creating
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Implement `HttpClientInterface::withOptions()` from `symfony/contracts` v2.4
|
||||
* Add `DecoratorTrait` to ease writing simple decorators
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* added `AsyncDecoratorTrait` to ease processing responses without breaking async
|
||||
* added support for pausing responses with a new `pause_handler` callable exposed as an info item
|
||||
* added `StreamableInterface` to ease turning responses into PHP streams
|
||||
* added `MockResponse::getRequestMethod()` and `getRequestUrl()` to allow inspecting which request has been sent
|
||||
* added `EventSourceHttpClient` a Server-Sent events stream implementing the [EventSource specification](https://www.w3.org/TR/eventsource/#eventsource)
|
||||
* added option "extra.curl" to allow setting additional curl options in `CurlHttpClient`
|
||||
* added `RetryableHttpClient` to automatically retry failed HTTP requests.
|
||||
* added `extra.trace_content` option to `TraceableHttpClient` to prevent it from keeping the content in memory
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* added `NoPrivateNetworkHttpClient` decorator
|
||||
* added `AmpHttpClient`, a portable HTTP/2 implementation based on Amp
|
||||
* added `LoggerAwareInterface` to `ScopingHttpClient` and `TraceableHttpClient`
|
||||
* made `HttpClient::create()` return an `AmpHttpClient` when `amphp/http-client` is found but curl is not or too old
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* added `canceled` to `ResponseInterface::getInfo()`
|
||||
* added `HttpClient::createForBaseUri()`
|
||||
* added `HttplugClient` with support for sync and async requests
|
||||
* added `max_duration` option
|
||||
* added support for NTLM authentication
|
||||
* added `StreamWrapper` to cast any `ResponseInterface` instances to PHP streams.
|
||||
* added `$response->toStream()` to cast responses to regular PHP streams
|
||||
* made `Psr18Client` implement relevant PSR-17 factories and have streaming responses
|
||||
* added `TraceableHttpClient`, `HttpClientDataCollector` and `HttpClientPass` to integrate with the web profiler
|
||||
* allow enabling buffering conditionally with a Closure
|
||||
* allow option "buffer" to be a stream resource
|
||||
* allow arbitrary values for the "json" option
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* added the component
|
||||
152
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/CachingHttpClient.php
vendored
Normal file
152
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/CachingHttpClient.php
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\HttpCache\HttpCache;
|
||||
use Symfony\Component\HttpKernel\HttpCache\StoreInterface;
|
||||
use Symfony\Component\HttpKernel\HttpClientKernel;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Adds caching on top of an HTTP client.
|
||||
*
|
||||
* The implementation buffers responses in memory and doesn't stream directly from the network.
|
||||
* You can disable/enable this layer by setting option "no_cache" under "extra" to true/false.
|
||||
* By default, caching is enabled unless the "buffer" option is set to false.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class CachingHttpClient implements HttpClientInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
|
||||
private $client;
|
||||
private $cache;
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
|
||||
public function __construct(HttpClientInterface $client, StoreInterface $store, array $defaultOptions = [])
|
||||
{
|
||||
if (!class_exists(HttpClientKernel::class)) {
|
||||
throw new \LogicException(sprintf('Using "%s" requires that the HttpKernel component version 4.3 or higher is installed, try running "composer require symfony/http-kernel:^5.4".', __CLASS__));
|
||||
}
|
||||
|
||||
$this->client = $client;
|
||||
$kernel = new HttpClientKernel($client);
|
||||
$this->cache = new HttpCache($kernel, $store, null, $defaultOptions);
|
||||
|
||||
unset($defaultOptions['debug']);
|
||||
unset($defaultOptions['default_ttl']);
|
||||
unset($defaultOptions['private_headers']);
|
||||
unset($defaultOptions['allow_reload']);
|
||||
unset($defaultOptions['allow_revalidate']);
|
||||
unset($defaultOptions['stale_while_revalidate']);
|
||||
unset($defaultOptions['stale_if_error']);
|
||||
unset($defaultOptions['trace_level']);
|
||||
unset($defaultOptions['trace_header']);
|
||||
|
||||
if ($defaultOptions) {
|
||||
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
[$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true);
|
||||
$url = implode('', $url);
|
||||
|
||||
if (!empty($options['body']) || !empty($options['extra']['no_cache']) || !\in_array($method, ['GET', 'HEAD', 'OPTIONS'])) {
|
||||
return $this->client->request($method, $url, $options);
|
||||
}
|
||||
|
||||
$request = Request::create($url, $method);
|
||||
$request->attributes->set('http_client_options', $options);
|
||||
|
||||
foreach ($options['normalized_headers'] as $name => $values) {
|
||||
if ('cookie' !== $name) {
|
||||
foreach ($values as $value) {
|
||||
$request->headers->set($name, substr($value, 2 + \strlen($name)), false);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($values as $cookies) {
|
||||
foreach (explode('; ', substr($cookies, \strlen('Cookie: '))) as $cookie) {
|
||||
if ('' !== $cookie) {
|
||||
$cookie = explode('=', $cookie, 2);
|
||||
$request->cookies->set($cookie[0], $cookie[1] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = $this->cache->handle($request);
|
||||
$response = new MockResponse($response->getContent(), [
|
||||
'http_code' => $response->getStatusCode(),
|
||||
'response_headers' => $response->headers->allPreserveCase(),
|
||||
]);
|
||||
|
||||
return MockResponse::fromRequest($method, $url, $options, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
if ($responses instanceof ResponseInterface) {
|
||||
$responses = [$responses];
|
||||
} elseif (!is_iterable($responses)) {
|
||||
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of ResponseInterface objects, "%s" given.', __METHOD__, get_debug_type($responses)));
|
||||
}
|
||||
|
||||
$mockResponses = [];
|
||||
$clientResponses = [];
|
||||
|
||||
foreach ($responses as $response) {
|
||||
if ($response instanceof MockResponse) {
|
||||
$mockResponses[] = $response;
|
||||
} else {
|
||||
$clientResponses[] = $response;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$mockResponses) {
|
||||
return $this->client->stream($clientResponses, $timeout);
|
||||
}
|
||||
|
||||
if (!$clientResponses) {
|
||||
return new ResponseStream(MockResponse::stream($mockResponses, $timeout));
|
||||
}
|
||||
|
||||
return new ResponseStream((function () use ($mockResponses, $clientResponses, $timeout) {
|
||||
yield from MockResponse::stream($mockResponses, $timeout);
|
||||
yield $this->client->stream($clientResponses, $timeout);
|
||||
})());
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
87
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Chunk/DataChunk.php
vendored
Normal file
87
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Chunk/DataChunk.php
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class DataChunk implements ChunkInterface
|
||||
{
|
||||
private $offset = 0;
|
||||
private $content = '';
|
||||
|
||||
public function __construct(int $offset = 0, string $content = '')
|
||||
{
|
||||
$this->offset = $offset;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTimeout(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInformationalStatus(): ?array
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent(): string
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOffset(): int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getError(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
140
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Chunk/ErrorChunk.php
vendored
Normal file
140
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Chunk/ErrorChunk.php
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TimeoutException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ErrorChunk implements ChunkInterface
|
||||
{
|
||||
private $didThrow = false;
|
||||
private $offset;
|
||||
private $errorMessage;
|
||||
private $error;
|
||||
|
||||
/**
|
||||
* @param \Throwable|string $error
|
||||
*/
|
||||
public function __construct(int $offset, $error)
|
||||
{
|
||||
$this->offset = $offset;
|
||||
|
||||
if (\is_string($error)) {
|
||||
$this->errorMessage = $error;
|
||||
} else {
|
||||
$this->error = $error;
|
||||
$this->errorMessage = $error->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isTimeout(): bool
|
||||
{
|
||||
$this->didThrow = true;
|
||||
|
||||
if (null !== $this->error) {
|
||||
throw new TransportException($this->errorMessage, 0, $this->error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
$this->didThrow = true;
|
||||
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
$this->didThrow = true;
|
||||
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInformationalStatus(): ?array
|
||||
{
|
||||
$this->didThrow = true;
|
||||
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent(): string
|
||||
{
|
||||
$this->didThrow = true;
|
||||
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOffset(): int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getError(): ?string
|
||||
{
|
||||
return $this->errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool Whether the wrapped error has been thrown or not
|
||||
*/
|
||||
public function didThrow(?bool $didThrow = null): bool
|
||||
{
|
||||
if (null !== $didThrow && $this->didThrow !== $didThrow) {
|
||||
return !$this->didThrow = $didThrow;
|
||||
}
|
||||
|
||||
return $this->didThrow;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
if (!$this->didThrow) {
|
||||
$this->didThrow = true;
|
||||
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FirstChunk extends DataChunk
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class InformationalChunk extends DataChunk
|
||||
{
|
||||
private $status;
|
||||
|
||||
public function __construct(int $statusCode, array $headers)
|
||||
{
|
||||
$this->status = [$statusCode, $headers];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInformationalStatus(): ?array
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
}
|
||||
28
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Chunk/LastChunk.php
vendored
Normal file
28
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Chunk/LastChunk.php
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class LastChunk extends DataChunk
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
|
||||
/**
|
||||
* @author Antoine Bluchet <soyuka@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class ServerSentEvent extends DataChunk implements ChunkInterface
|
||||
{
|
||||
private $data = '';
|
||||
private $id = '';
|
||||
private $type = 'message';
|
||||
private $retry = 0;
|
||||
|
||||
public function __construct(string $content)
|
||||
{
|
||||
parent::__construct(-1, $content);
|
||||
|
||||
// remove BOM
|
||||
if (0 === strpos($content, "\xEF\xBB\xBF")) {
|
||||
$content = substr($content, 3);
|
||||
}
|
||||
|
||||
foreach (preg_split("/(?:\r\n|[\r\n])/", $content) as $line) {
|
||||
if (0 === $i = strpos($line, ':')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$i = false === $i ? \strlen($line) : $i;
|
||||
$field = substr($line, 0, $i);
|
||||
$i += 1 + (' ' === ($line[1 + $i] ?? ''));
|
||||
|
||||
switch ($field) {
|
||||
case 'id': $this->id = substr($line, $i); break;
|
||||
case 'event': $this->type = substr($line, $i); break;
|
||||
case 'data': $this->data .= ('' === $this->data ? '' : "\n").substr($line, $i); break;
|
||||
case 'retry':
|
||||
$retry = substr($line, $i);
|
||||
|
||||
if ('' !== $retry && \strlen($retry) === strspn($retry, '0123456789')) {
|
||||
$this->retry = $retry / 1000.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getData(): string
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getRetry(): float
|
||||
{
|
||||
return $this->retry;
|
||||
}
|
||||
}
|
||||
574
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/CurlHttpClient.php
vendored
Normal file
574
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/CurlHttpClient.php
vendored
Normal file
@@ -0,0 +1,574 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\CurlClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\PushedResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\CurlResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* A performant implementation of the HttpClientInterface contracts based on the curl extension.
|
||||
*
|
||||
* This provides fully concurrent HTTP requests, with transparent
|
||||
* HTTP/2 push when a curl version that supports it is installed.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS + [
|
||||
'auth_ntlm' => null, // array|string - an array containing the username as first value, and optionally the
|
||||
// password as the second one; or string like username:password - enabling NTLM auth
|
||||
'extra' => [
|
||||
'curl' => [], // A list of extra curl options indexed by their corresponding CURLOPT_*
|
||||
],
|
||||
];
|
||||
private static $emptyDefaults = self::OPTIONS_DEFAULTS + ['auth_ntlm' => null];
|
||||
|
||||
/**
|
||||
* @var LoggerInterface|null
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
private $maxHostConnections;
|
||||
private $maxPendingPushes;
|
||||
|
||||
/**
|
||||
* An internal object to share state between the client and its responses.
|
||||
*
|
||||
* @var CurlClientState
|
||||
*/
|
||||
private $multi;
|
||||
|
||||
/**
|
||||
* @param array $defaultOptions Default request's options
|
||||
* @param int $maxHostConnections The maximum number of connections to a single host
|
||||
* @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue
|
||||
*
|
||||
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
|
||||
*/
|
||||
public function __construct(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 0)
|
||||
{
|
||||
if (!\extension_loaded('curl')) {
|
||||
throw new \LogicException('You cannot use the "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.');
|
||||
}
|
||||
|
||||
$this->maxHostConnections = $maxHostConnections;
|
||||
$this->maxPendingPushes = $maxPendingPushes;
|
||||
|
||||
$this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']);
|
||||
|
||||
if ($defaultOptions) {
|
||||
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
|
||||
}
|
||||
}
|
||||
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
$this->logger = $logger;
|
||||
if (isset($this->multi)) {
|
||||
$this->multi->logger = $logger;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$multi = $this->ensureState();
|
||||
|
||||
[$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions);
|
||||
$scheme = $url['scheme'];
|
||||
$authority = $url['authority'];
|
||||
$host = parse_url($authority, \PHP_URL_HOST);
|
||||
$proxy = self::getProxyUrl($options['proxy'], $url);
|
||||
$url = implode('', $url);
|
||||
|
||||
if (!isset($options['normalized_headers']['user-agent'])) {
|
||||
$options['headers'][] = 'User-Agent: Symfony HttpClient/Curl';
|
||||
}
|
||||
|
||||
$curlopts = [
|
||||
\CURLOPT_URL => $url,
|
||||
\CURLOPT_TCP_NODELAY => true,
|
||||
\CURLOPT_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS,
|
||||
\CURLOPT_REDIR_PROTOCOLS => \CURLPROTO_HTTP | \CURLPROTO_HTTPS,
|
||||
\CURLOPT_FOLLOWLOCATION => true,
|
||||
\CURLOPT_MAXREDIRS => 0 < $options['max_redirects'] ? $options['max_redirects'] : 0,
|
||||
\CURLOPT_COOKIEFILE => '', // Keep track of cookies during redirects
|
||||
\CURLOPT_TIMEOUT => 0,
|
||||
\CURLOPT_PROXY => $proxy,
|
||||
\CURLOPT_NOPROXY => $options['no_proxy'] ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '',
|
||||
\CURLOPT_SSL_VERIFYPEER => $options['verify_peer'],
|
||||
\CURLOPT_SSL_VERIFYHOST => $options['verify_host'] ? 2 : 0,
|
||||
\CURLOPT_CAINFO => $options['cafile'],
|
||||
\CURLOPT_CAPATH => $options['capath'],
|
||||
\CURLOPT_SSL_CIPHER_LIST => $options['ciphers'],
|
||||
\CURLOPT_SSLCERT => $options['local_cert'],
|
||||
\CURLOPT_SSLKEY => $options['local_pk'],
|
||||
\CURLOPT_KEYPASSWD => $options['passphrase'],
|
||||
\CURLOPT_CERTINFO => $options['capture_peer_cert_chain'],
|
||||
];
|
||||
|
||||
if (1.0 === (float) $options['http_version']) {
|
||||
$curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_0;
|
||||
} elseif (1.1 === (float) $options['http_version']) {
|
||||
$curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
|
||||
} elseif (\defined('CURL_VERSION_HTTP2') && (\CURL_VERSION_HTTP2 & CurlClientState::$curlVersion['features']) && ('https:' === $scheme || 2.0 === (float) $options['http_version'])) {
|
||||
$curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_2_0;
|
||||
}
|
||||
|
||||
if (isset($options['auth_ntlm'])) {
|
||||
$curlopts[\CURLOPT_HTTPAUTH] = \CURLAUTH_NTLM;
|
||||
$curlopts[\CURLOPT_HTTP_VERSION] = \CURL_HTTP_VERSION_1_1;
|
||||
|
||||
if (\is_array($options['auth_ntlm'])) {
|
||||
$count = \count($options['auth_ntlm']);
|
||||
if ($count <= 0 || $count > 2) {
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must contain 1 or 2 elements, %d given.', $count));
|
||||
}
|
||||
|
||||
$options['auth_ntlm'] = implode(':', $options['auth_ntlm']);
|
||||
}
|
||||
|
||||
if (!\is_string($options['auth_ntlm'])) {
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_ntlm" must be a string or an array, "%s" given.', get_debug_type($options['auth_ntlm'])));
|
||||
}
|
||||
|
||||
$curlopts[\CURLOPT_USERPWD] = $options['auth_ntlm'];
|
||||
}
|
||||
|
||||
if (!\ZEND_THREAD_SAFE) {
|
||||
$curlopts[\CURLOPT_DNS_USE_GLOBAL_CACHE] = false;
|
||||
}
|
||||
|
||||
if (\defined('CURLOPT_HEADEROPT') && \defined('CURLHEADER_SEPARATE')) {
|
||||
$curlopts[\CURLOPT_HEADEROPT] = \CURLHEADER_SEPARATE;
|
||||
}
|
||||
|
||||
// curl's resolve feature varies by host:port but ours varies by host only, let's handle this with our own DNS map
|
||||
if (isset($multi->dnsCache->hostnames[$host])) {
|
||||
$options['resolve'] += [$host => $multi->dnsCache->hostnames[$host]];
|
||||
}
|
||||
|
||||
if ($options['resolve'] || $multi->dnsCache->evictions) {
|
||||
// First reset any old DNS cache entries then add the new ones
|
||||
$resolve = $multi->dnsCache->evictions;
|
||||
$multi->dnsCache->evictions = [];
|
||||
$port = parse_url($authority, \PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
|
||||
|
||||
if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) {
|
||||
// DNS cache removals require curl 7.42 or higher
|
||||
$multi->reset();
|
||||
}
|
||||
|
||||
foreach ($options['resolve'] as $resolveHost => $ip) {
|
||||
$resolve[] = null === $ip ? "-$resolveHost:$port" : "$resolveHost:$port:$ip";
|
||||
$multi->dnsCache->hostnames[$resolveHost] = $ip;
|
||||
$multi->dnsCache->removals["-$resolveHost:$port"] = "-$resolveHost:$port";
|
||||
}
|
||||
|
||||
$curlopts[\CURLOPT_RESOLVE] = $resolve;
|
||||
}
|
||||
|
||||
if ('POST' === $method) {
|
||||
// Use CURLOPT_POST to have browser-like POST-to-GET redirects for 301, 302 and 303
|
||||
$curlopts[\CURLOPT_POST] = true;
|
||||
} elseif ('HEAD' === $method) {
|
||||
$curlopts[\CURLOPT_NOBODY] = true;
|
||||
} else {
|
||||
$curlopts[\CURLOPT_CUSTOMREQUEST] = $method;
|
||||
}
|
||||
|
||||
if ('\\' !== \DIRECTORY_SEPARATOR && $options['timeout'] < 1) {
|
||||
$curlopts[\CURLOPT_NOSIGNAL] = true;
|
||||
}
|
||||
|
||||
if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) {
|
||||
$options['headers'][] = 'Accept-Encoding: gzip'; // Expose only one encoding, some servers mess up when more are provided
|
||||
}
|
||||
$body = $options['body'];
|
||||
|
||||
foreach ($options['headers'] as $i => $header) {
|
||||
if (\is_string($body) && '' !== $body && 0 === stripos($header, 'Content-Length: ')) {
|
||||
// Let curl handle Content-Length headers
|
||||
unset($options['headers'][$i]);
|
||||
continue;
|
||||
}
|
||||
if (':' === $header[-2] && \strlen($header) - 2 === strpos($header, ': ')) {
|
||||
// curl requires a special syntax to send empty headers
|
||||
$curlopts[\CURLOPT_HTTPHEADER][] = substr_replace($header, ';', -2);
|
||||
} else {
|
||||
$curlopts[\CURLOPT_HTTPHEADER][] = $header;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent curl from sending its default Accept and Expect headers
|
||||
foreach (['accept', 'expect'] as $header) {
|
||||
if (!isset($options['normalized_headers'][$header][0])) {
|
||||
$curlopts[\CURLOPT_HTTPHEADER][] = $header.':';
|
||||
}
|
||||
}
|
||||
|
||||
if (!\is_string($body)) {
|
||||
if (\is_resource($body)) {
|
||||
$curlopts[\CURLOPT_INFILE] = $body;
|
||||
} else {
|
||||
$eof = false;
|
||||
$buffer = '';
|
||||
$curlopts[\CURLOPT_READFUNCTION] = static function ($ch, $fd, $length) use ($body, &$buffer, &$eof) {
|
||||
return self::readRequestBody($length, $body, $buffer, $eof);
|
||||
};
|
||||
}
|
||||
|
||||
if (isset($options['normalized_headers']['content-length'][0])) {
|
||||
$curlopts[\CURLOPT_INFILESIZE] = (int) substr($options['normalized_headers']['content-length'][0], \strlen('Content-Length: '));
|
||||
}
|
||||
if (!isset($options['normalized_headers']['transfer-encoding'])) {
|
||||
$curlopts[\CURLOPT_HTTPHEADER][] = 'Transfer-Encoding:'.(isset($curlopts[\CURLOPT_INFILESIZE]) ? '' : ' chunked');
|
||||
}
|
||||
|
||||
if ('POST' !== $method) {
|
||||
$curlopts[\CURLOPT_UPLOAD] = true;
|
||||
|
||||
if (!isset($options['normalized_headers']['content-type']) && 0 !== ($curlopts[\CURLOPT_INFILESIZE] ?? null)) {
|
||||
$curlopts[\CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded';
|
||||
}
|
||||
}
|
||||
} elseif ('' !== $body || 'POST' === $method) {
|
||||
$curlopts[\CURLOPT_POSTFIELDS] = $body;
|
||||
}
|
||||
|
||||
if ($options['peer_fingerprint']) {
|
||||
if (!isset($options['peer_fingerprint']['pin-sha256'])) {
|
||||
throw new TransportException(__CLASS__.' supports only "pin-sha256" fingerprints.');
|
||||
}
|
||||
|
||||
$curlopts[\CURLOPT_PINNEDPUBLICKEY] = 'sha256//'.implode(';sha256//', $options['peer_fingerprint']['pin-sha256']);
|
||||
}
|
||||
|
||||
if ($options['bindto']) {
|
||||
if (file_exists($options['bindto'])) {
|
||||
$curlopts[\CURLOPT_UNIX_SOCKET_PATH] = $options['bindto'];
|
||||
} elseif (!str_starts_with($options['bindto'], 'if!') && preg_match('/^(.*):(\d+)$/', $options['bindto'], $matches)) {
|
||||
$curlopts[\CURLOPT_INTERFACE] = trim($matches[1], '[]');
|
||||
$curlopts[\CURLOPT_LOCALPORT] = $matches[2];
|
||||
} else {
|
||||
$curlopts[\CURLOPT_INTERFACE] = $options['bindto'];
|
||||
}
|
||||
}
|
||||
|
||||
if (0 < $options['max_duration']) {
|
||||
$curlopts[\CURLOPT_TIMEOUT_MS] = 1000 * $options['max_duration'];
|
||||
}
|
||||
|
||||
if (!empty($options['extra']['curl']) && \is_array($options['extra']['curl'])) {
|
||||
$this->validateExtraCurlOptions($options['extra']['curl']);
|
||||
$curlopts += $options['extra']['curl'];
|
||||
}
|
||||
|
||||
if ($pushedResponse = $multi->pushedResponses[$url] ?? null) {
|
||||
unset($multi->pushedResponses[$url]);
|
||||
|
||||
if (self::acceptPushForRequest($method, $options, $pushedResponse)) {
|
||||
$this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url));
|
||||
|
||||
// Reinitialize the pushed response with request's options
|
||||
$ch = $pushedResponse->handle;
|
||||
$pushedResponse = $pushedResponse->response;
|
||||
$pushedResponse->__construct($multi, $url, $options, $this->logger);
|
||||
} else {
|
||||
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s"', $url));
|
||||
$pushedResponse = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$pushedResponse) {
|
||||
$ch = curl_init();
|
||||
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url));
|
||||
$curlopts += [\CURLOPT_SHARE => $multi->share];
|
||||
}
|
||||
|
||||
foreach ($curlopts as $opt => $value) {
|
||||
if (null !== $value && !curl_setopt($ch, $opt, $value) && \CURLOPT_CERTINFO !== $opt && (!\defined('CURLOPT_HEADEROPT') || \CURLOPT_HEADEROPT !== $opt)) {
|
||||
$constantName = $this->findConstantName($opt);
|
||||
throw new TransportException(sprintf('Curl option "%s" is not supported.', $constantName ?? $opt));
|
||||
}
|
||||
}
|
||||
|
||||
return $pushedResponse ?? new CurlResponse($multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host), CurlClientState::$curlVersion['version_number']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
if ($responses instanceof CurlResponse) {
|
||||
$responses = [$responses];
|
||||
} elseif (!is_iterable($responses)) {
|
||||
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of CurlResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
|
||||
}
|
||||
|
||||
$multi = $this->ensureState();
|
||||
|
||||
if (\is_resource($multi->handle) || $multi->handle instanceof \CurlMultiHandle) {
|
||||
$active = 0;
|
||||
while (\CURLM_CALL_MULTI_PERFORM === curl_multi_exec($multi->handle, $active)) {
|
||||
}
|
||||
}
|
||||
|
||||
return new ResponseStream(CurlResponse::stream($responses, $timeout));
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if (isset($this->multi)) {
|
||||
$this->multi->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts pushed responses only if their headers related to authentication match the request.
|
||||
*/
|
||||
private static function acceptPushForRequest(string $method, array $options, PushedResponse $pushedResponse): bool
|
||||
{
|
||||
if ('' !== $options['body'] || $method !== $pushedResponse->requestHeaders[':method'][0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (['proxy', 'no_proxy', 'bindto', 'local_cert', 'local_pk'] as $k) {
|
||||
if ($options[$k] !== $pushedResponse->parentOptions[$k]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) {
|
||||
$normalizedHeaders = $options['normalized_headers'][$k] ?? [];
|
||||
foreach ($normalizedHeaders as $i => $v) {
|
||||
$normalizedHeaders[$i] = substr($v, \strlen($k) + 2);
|
||||
}
|
||||
|
||||
if (($pushedResponse->requestHeaders[$k] ?? []) !== $normalizedHeaders) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the request's body callback to allow it to return strings longer than curl requested.
|
||||
*/
|
||||
private static function readRequestBody(int $length, \Closure $body, string &$buffer, bool &$eof): string
|
||||
{
|
||||
if (!$eof && \strlen($buffer) < $length) {
|
||||
if (!\is_string($data = $body($length))) {
|
||||
throw new TransportException(sprintf('The return value of the "body" option callback must be a string, "%s" returned.', get_debug_type($data)));
|
||||
}
|
||||
|
||||
$buffer .= $data;
|
||||
$eof = '' === $data;
|
||||
}
|
||||
|
||||
$data = substr($buffer, 0, $length);
|
||||
$buffer = substr($buffer, $length);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves relative URLs on redirects and deals with authentication headers.
|
||||
*
|
||||
* Work around CVE-2018-1000007: Authorization and Cookie headers should not follow redirects - fixed in Curl 7.64
|
||||
*/
|
||||
private static function createRedirectResolver(array $options, string $host): \Closure
|
||||
{
|
||||
$redirectHeaders = [];
|
||||
if (0 < $options['max_redirects']) {
|
||||
$redirectHeaders['host'] = $host;
|
||||
$redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
|
||||
return 0 !== stripos($h, 'Host:');
|
||||
});
|
||||
|
||||
if (isset($options['normalized_headers']['authorization'][0]) || isset($options['normalized_headers']['cookie'][0])) {
|
||||
$redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
|
||||
return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) {
|
||||
try {
|
||||
$location = self::parseUrl($location);
|
||||
$url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL));
|
||||
$url = self::resolveUrl($location, $url);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($noContent && $redirectHeaders) {
|
||||
$filterContentHeaders = static function ($h) {
|
||||
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
|
||||
};
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
|
||||
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
|
||||
}
|
||||
|
||||
if ($redirectHeaders && isset($location['authority'])) {
|
||||
$requestHeaders = parse_url($location['authority'], \PHP_URL_HOST) === $redirectHeaders['host'] ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
|
||||
} elseif ($noContent && $redirectHeaders) {
|
||||
curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_PROXY, self::getProxyUrl($options['proxy'], $url));
|
||||
|
||||
return implode('', $url);
|
||||
};
|
||||
}
|
||||
|
||||
private function ensureState(): CurlClientState
|
||||
{
|
||||
if (!isset($this->multi)) {
|
||||
$this->multi = new CurlClientState($this->maxHostConnections, $this->maxPendingPushes);
|
||||
$this->multi->logger = $this->logger;
|
||||
}
|
||||
|
||||
return $this->multi;
|
||||
}
|
||||
|
||||
private function findConstantName(int $opt): ?string
|
||||
{
|
||||
$constants = array_filter(get_defined_constants(), static function ($v, $k) use ($opt) {
|
||||
return $v === $opt && 'C' === $k[0] && (str_starts_with($k, 'CURLOPT_') || str_starts_with($k, 'CURLINFO_'));
|
||||
}, \ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
return key($constants);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents overriding options that are set internally throughout the request.
|
||||
*/
|
||||
private function validateExtraCurlOptions(array $options): void
|
||||
{
|
||||
$curloptsToConfig = [
|
||||
// options used in CurlHttpClient
|
||||
\CURLOPT_HTTPAUTH => 'auth_ntlm',
|
||||
\CURLOPT_USERPWD => 'auth_ntlm',
|
||||
\CURLOPT_RESOLVE => 'resolve',
|
||||
\CURLOPT_NOSIGNAL => 'timeout',
|
||||
\CURLOPT_HTTPHEADER => 'headers',
|
||||
\CURLOPT_INFILE => 'body',
|
||||
\CURLOPT_READFUNCTION => 'body',
|
||||
\CURLOPT_INFILESIZE => 'body',
|
||||
\CURLOPT_POSTFIELDS => 'body',
|
||||
\CURLOPT_UPLOAD => 'body',
|
||||
\CURLOPT_INTERFACE => 'bindto',
|
||||
\CURLOPT_TIMEOUT_MS => 'max_duration',
|
||||
\CURLOPT_TIMEOUT => 'max_duration',
|
||||
\CURLOPT_MAXREDIRS => 'max_redirects',
|
||||
\CURLOPT_POSTREDIR => 'max_redirects',
|
||||
\CURLOPT_PROXY => 'proxy',
|
||||
\CURLOPT_NOPROXY => 'no_proxy',
|
||||
\CURLOPT_SSL_VERIFYPEER => 'verify_peer',
|
||||
\CURLOPT_SSL_VERIFYHOST => 'verify_host',
|
||||
\CURLOPT_CAINFO => 'cafile',
|
||||
\CURLOPT_CAPATH => 'capath',
|
||||
\CURLOPT_SSL_CIPHER_LIST => 'ciphers',
|
||||
\CURLOPT_SSLCERT => 'local_cert',
|
||||
\CURLOPT_SSLKEY => 'local_pk',
|
||||
\CURLOPT_KEYPASSWD => 'passphrase',
|
||||
\CURLOPT_CERTINFO => 'capture_peer_cert_chain',
|
||||
\CURLOPT_USERAGENT => 'normalized_headers',
|
||||
\CURLOPT_REFERER => 'headers',
|
||||
// options used in CurlResponse
|
||||
\CURLOPT_NOPROGRESS => 'on_progress',
|
||||
\CURLOPT_PROGRESSFUNCTION => 'on_progress',
|
||||
];
|
||||
|
||||
if (\defined('CURLOPT_UNIX_SOCKET_PATH')) {
|
||||
$curloptsToConfig[\CURLOPT_UNIX_SOCKET_PATH] = 'bindto';
|
||||
}
|
||||
|
||||
if (\defined('CURLOPT_PINNEDPUBLICKEY')) {
|
||||
$curloptsToConfig[\CURLOPT_PINNEDPUBLICKEY] = 'peer_fingerprint';
|
||||
}
|
||||
|
||||
$curloptsToCheck = [
|
||||
\CURLOPT_PRIVATE,
|
||||
\CURLOPT_HEADERFUNCTION,
|
||||
\CURLOPT_WRITEFUNCTION,
|
||||
\CURLOPT_VERBOSE,
|
||||
\CURLOPT_STDERR,
|
||||
\CURLOPT_RETURNTRANSFER,
|
||||
\CURLOPT_URL,
|
||||
\CURLOPT_FOLLOWLOCATION,
|
||||
\CURLOPT_HEADER,
|
||||
\CURLOPT_CONNECTTIMEOUT,
|
||||
\CURLOPT_CONNECTTIMEOUT_MS,
|
||||
\CURLOPT_HTTP_VERSION,
|
||||
\CURLOPT_PORT,
|
||||
\CURLOPT_DNS_USE_GLOBAL_CACHE,
|
||||
\CURLOPT_PROTOCOLS,
|
||||
\CURLOPT_REDIR_PROTOCOLS,
|
||||
\CURLOPT_COOKIEFILE,
|
||||
\CURLINFO_REDIRECT_COUNT,
|
||||
];
|
||||
|
||||
if (\defined('CURLOPT_HTTP09_ALLOWED')) {
|
||||
$curloptsToCheck[] = \CURLOPT_HTTP09_ALLOWED;
|
||||
}
|
||||
|
||||
if (\defined('CURLOPT_HEADEROPT')) {
|
||||
$curloptsToCheck[] = \CURLOPT_HEADEROPT;
|
||||
}
|
||||
|
||||
$methodOpts = [
|
||||
\CURLOPT_POST,
|
||||
\CURLOPT_PUT,
|
||||
\CURLOPT_CUSTOMREQUEST,
|
||||
\CURLOPT_HTTPGET,
|
||||
\CURLOPT_NOBODY,
|
||||
];
|
||||
|
||||
foreach ($options as $opt => $optValue) {
|
||||
if (isset($curloptsToConfig[$opt])) {
|
||||
$constName = $this->findConstantName($opt) ?? $opt;
|
||||
throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl", use option "%s" instead.', $constName, $curloptsToConfig[$opt]));
|
||||
}
|
||||
|
||||
if (\in_array($opt, $methodOpts)) {
|
||||
throw new InvalidArgumentException('The HTTP method cannot be overridden using "extra.curl".');
|
||||
}
|
||||
|
||||
if (\in_array($opt, $curloptsToCheck)) {
|
||||
$constName = $this->findConstantName($opt) ?? $opt;
|
||||
throw new InvalidArgumentException(sprintf('Cannot set "%s" with "extra.curl".', $constName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\DataCollector;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\TraceableHttpClient;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation\Request;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
use Symfony\Component\VarDumper\Caster\ImgStub;
|
||||
|
||||
/**
|
||||
* @author Jérémy Romey <jeremy@free-agent.fr>
|
||||
*/
|
||||
final class HttpClientDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
/**
|
||||
* @var TraceableHttpClient[]
|
||||
*/
|
||||
private $clients = [];
|
||||
|
||||
public function registerClient(string $name, TraceableHttpClient $client)
|
||||
{
|
||||
$this->clients[$name] = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collect(Request $request, Response $response, ?\Throwable $exception = null)
|
||||
{
|
||||
$this->lateCollect();
|
||||
}
|
||||
|
||||
public function lateCollect()
|
||||
{
|
||||
$this->data['request_count'] = $this->data['request_count'] ?? 0;
|
||||
$this->data['error_count'] = $this->data['error_count'] ?? 0;
|
||||
$this->data += ['clients' => []];
|
||||
|
||||
foreach ($this->clients as $name => $client) {
|
||||
[$errorCount, $traces] = $this->collectOnClient($client);
|
||||
|
||||
$this->data['clients'] += [
|
||||
$name => [
|
||||
'traces' => [],
|
||||
'error_count' => 0,
|
||||
],
|
||||
];
|
||||
|
||||
$this->data['clients'][$name]['traces'] = array_merge($this->data['clients'][$name]['traces'], $traces);
|
||||
$this->data['request_count'] += \count($traces);
|
||||
$this->data['error_count'] += $errorCount;
|
||||
$this->data['clients'][$name]['error_count'] += $errorCount;
|
||||
|
||||
$client->reset();
|
||||
}
|
||||
}
|
||||
|
||||
public function getClients(): array
|
||||
{
|
||||
return $this->data['clients'] ?? [];
|
||||
}
|
||||
|
||||
public function getRequestCount(): int
|
||||
{
|
||||
return $this->data['request_count'] ?? 0;
|
||||
}
|
||||
|
||||
public function getErrorCount(): int
|
||||
{
|
||||
return $this->data['error_count'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'http_client';
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->data = [
|
||||
'clients' => [],
|
||||
'request_count' => 0,
|
||||
'error_count' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function collectOnClient(TraceableHttpClient $client): array
|
||||
{
|
||||
$traces = $client->getTracedRequests();
|
||||
$errorCount = 0;
|
||||
$baseInfo = [
|
||||
'response_headers' => 1,
|
||||
'retry_count' => 1,
|
||||
'redirect_count' => 1,
|
||||
'redirect_url' => 1,
|
||||
'user_data' => 1,
|
||||
'error' => 1,
|
||||
'url' => 1,
|
||||
];
|
||||
|
||||
foreach ($traces as $i => $trace) {
|
||||
if (400 <= ($trace['info']['http_code'] ?? 0)) {
|
||||
++$errorCount;
|
||||
}
|
||||
|
||||
$info = $trace['info'];
|
||||
$traces[$i]['http_code'] = $info['http_code'] ?? 0;
|
||||
|
||||
unset($info['filetime'], $info['http_code'], $info['ssl_verify_result'], $info['content_type']);
|
||||
|
||||
if (($info['http_method'] ?? null) === $trace['method']) {
|
||||
unset($info['http_method']);
|
||||
}
|
||||
|
||||
if (($info['url'] ?? null) === $trace['url']) {
|
||||
unset($info['url']);
|
||||
}
|
||||
|
||||
foreach ($info as $k => $v) {
|
||||
if (!$v || (is_numeric($v) && 0 > $v)) {
|
||||
unset($info[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_string($content = $trace['content'])) {
|
||||
$contentType = 'application/octet-stream';
|
||||
|
||||
foreach ($info['response_headers'] ?? [] as $h) {
|
||||
if (0 === stripos($h, 'content-type: ')) {
|
||||
$contentType = substr($h, \strlen('content-type: '));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 === strpos($contentType, 'image/') && class_exists(ImgStub::class)) {
|
||||
$content = new ImgStub($content, $contentType, '');
|
||||
} else {
|
||||
$content = [$content];
|
||||
}
|
||||
|
||||
$content = ['response_content' => $content];
|
||||
} elseif (\is_array($content)) {
|
||||
$content = ['response_json' => $content];
|
||||
} else {
|
||||
$content = [];
|
||||
}
|
||||
|
||||
if (isset($info['retry_count'])) {
|
||||
$content['retries'] = $info['previous_info'];
|
||||
unset($info['previous_info']);
|
||||
}
|
||||
|
||||
$debugInfo = array_diff_key($info, $baseInfo);
|
||||
$info = ['info' => $debugInfo] + array_diff_key($info, $debugInfo) + $content;
|
||||
unset($traces[$i]['info']); // break PHP reference used by TraceableHttpClient
|
||||
$traces[$i]['info'] = $this->cloneVar($info);
|
||||
$traces[$i]['options'] = $this->cloneVar($trace['options']);
|
||||
}
|
||||
|
||||
return [$errorCount, $traces];
|
||||
}
|
||||
}
|
||||
66
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/DecoratorTrait.php
vendored
Normal file
66
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/DecoratorTrait.php
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Eases with writing decorators.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
trait DecoratorTrait
|
||||
{
|
||||
private $client;
|
||||
|
||||
public function __construct(?HttpClientInterface $client = null)
|
||||
{
|
||||
$this->client = $client ?? HttpClient::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->client->request($method, $url, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
return $this->client->stream($responses, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function withOptions(array $options): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->client = $this->client->withOptions($options);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\TraceableHttpClient;
|
||||
|
||||
final class HttpClientPass implements CompilerPassInterface
|
||||
{
|
||||
private $clientTag;
|
||||
|
||||
public function __construct(string $clientTag = 'http_client.client')
|
||||
{
|
||||
if (0 < \func_num_args()) {
|
||||
kadence_blocks_trigger_deprecation('symfony/http-client', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
|
||||
}
|
||||
|
||||
$this->clientTag = $clientTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('data_collector.http_client')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->clientTag) as $id => $tags) {
|
||||
$container->register('.debug.'.$id, TraceableHttpClient::class)
|
||||
->setArguments([new Reference('.debug.'.$id.'.inner'), new Reference('debug.stopwatch', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)])
|
||||
->addTag('kernel.reset', ['method' => 'reset'])
|
||||
->setDecoratedService($id, null, 5);
|
||||
$container->getDefinition('data_collector.http_client')
|
||||
->addMethodCall('registerClient', [$id, new Reference('.debug.'.$id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\DataChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\ServerSentEvent;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\EventSourceException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncContext;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* @author Antoine Bluchet <soyuka@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class EventSourceHttpClient implements HttpClientInterface, ResetInterface
|
||||
{
|
||||
use AsyncDecoratorTrait, HttpClientTrait {
|
||||
AsyncDecoratorTrait::withOptions insteadof HttpClientTrait;
|
||||
}
|
||||
|
||||
private $reconnectionTime;
|
||||
|
||||
public function __construct(?HttpClientInterface $client = null, float $reconnectionTime = 10.0)
|
||||
{
|
||||
$this->client = $client ?? HttpClient::create();
|
||||
$this->reconnectionTime = $reconnectionTime;
|
||||
}
|
||||
|
||||
public function connect(string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
return $this->request('GET', $url, self::mergeDefaultOptions($options, [
|
||||
'buffer' => false,
|
||||
'headers' => [
|
||||
'Accept' => 'text/event-stream',
|
||||
'Cache-Control' => 'no-cache',
|
||||
],
|
||||
], true));
|
||||
}
|
||||
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$state = new class() {
|
||||
public $buffer = null;
|
||||
public $lastEventId = null;
|
||||
public $reconnectionTime;
|
||||
public $lastError = null;
|
||||
};
|
||||
$state->reconnectionTime = $this->reconnectionTime;
|
||||
|
||||
if ($accept = self::normalizeHeaders($options['headers'] ?? [])['accept'] ?? []) {
|
||||
$state->buffer = \in_array($accept, [['Accept: text/event-stream'], ['accept: text/event-stream']], true) ? '' : null;
|
||||
|
||||
if (null !== $state->buffer) {
|
||||
$options['extra']['trace_content'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use ($state, $method, $url, $options) {
|
||||
if (null !== $state->buffer) {
|
||||
$context->setInfo('reconnection_time', $state->reconnectionTime);
|
||||
$isTimeout = false;
|
||||
}
|
||||
$lastError = $state->lastError;
|
||||
$state->lastError = null;
|
||||
|
||||
try {
|
||||
$isTimeout = $chunk->isTimeout();
|
||||
|
||||
if (null !== $chunk->getInformationalStatus() || $context->getInfo('canceled')) {
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$state->lastError = $lastError ?? microtime(true);
|
||||
|
||||
if (null === $state->buffer || ($isTimeout && microtime(true) - $state->lastError < $state->reconnectionTime)) {
|
||||
yield $chunk;
|
||||
} else {
|
||||
$options['headers']['Last-Event-ID'] = $state->lastEventId;
|
||||
$state->buffer = '';
|
||||
$state->lastError = microtime(true);
|
||||
$context->getResponse()->cancel();
|
||||
$context->replaceRequest($method, $url, $options);
|
||||
if ($isTimeout) {
|
||||
yield $chunk;
|
||||
} else {
|
||||
$context->pause($state->reconnectionTime);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($chunk->isFirst()) {
|
||||
if (preg_match('/^text\/event-stream(;|$)/i', $context->getHeaders()['content-type'][0] ?? '')) {
|
||||
$state->buffer = '';
|
||||
} elseif (null !== $lastError || (null !== $state->buffer && 200 === $context->getStatusCode())) {
|
||||
throw new EventSourceException(sprintf('Response content-type is "%s" while "text/event-stream" was expected for "%s".', $context->getHeaders()['content-type'][0] ?? '', $context->getInfo('url')));
|
||||
} else {
|
||||
$context->passthru();
|
||||
}
|
||||
|
||||
if (null === $lastError) {
|
||||
yield $chunk;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($chunk->isLast()) {
|
||||
if ('' !== $content = $state->buffer) {
|
||||
$state->buffer = '';
|
||||
yield new DataChunk(-1, $content);
|
||||
}
|
||||
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$content = $state->buffer.$chunk->getContent();
|
||||
$events = preg_split('/((?:\r\n){2,}|\r{2,}|\n{2,})/', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
|
||||
$state->buffer = array_pop($events);
|
||||
|
||||
for ($i = 0; isset($events[$i]); $i += 2) {
|
||||
$content = $events[$i].$events[1 + $i];
|
||||
if (!preg_match('/(?:^|\r\n|[\r\n])[^:\r\n]/', $content)) {
|
||||
yield new DataChunk(-1, $content);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$event = new ServerSentEvent($content);
|
||||
|
||||
if ('' !== $event->getId()) {
|
||||
$context->setInfo('last_event_id', $state->lastEventId = $event->getId());
|
||||
}
|
||||
|
||||
if ($event->getRetry()) {
|
||||
$context->setInfo('reconnection_time', $state->reconnectionTime = $event->getRetry());
|
||||
}
|
||||
|
||||
yield $event;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
|
||||
/**
|
||||
* Represents a 4xx response.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class ClientException extends \RuntimeException implements ClientExceptionInterface
|
||||
{
|
||||
use HttpExceptionTrait;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class EventSourceException extends \RuntimeException implements DecodingExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait HttpExceptionTrait
|
||||
{
|
||||
private $response;
|
||||
|
||||
public function __construct(ResponseInterface $response)
|
||||
{
|
||||
$this->response = $response;
|
||||
$code = $response->getInfo('http_code');
|
||||
$url = $response->getInfo('url');
|
||||
$message = sprintf('HTTP %d returned for "%s".', $code, $url);
|
||||
|
||||
$httpCodeFound = false;
|
||||
$isJson = false;
|
||||
foreach (array_reverse($response->getInfo('response_headers')) as $h) {
|
||||
if (str_starts_with($h, 'HTTP/')) {
|
||||
if ($httpCodeFound) {
|
||||
break;
|
||||
}
|
||||
|
||||
$message = sprintf('%s returned for "%s".', $h, $url);
|
||||
$httpCodeFound = true;
|
||||
}
|
||||
|
||||
if (0 === stripos($h, 'content-type:')) {
|
||||
if (preg_match('/\bjson\b/i', $h)) {
|
||||
$isJson = true;
|
||||
}
|
||||
|
||||
if ($httpCodeFound) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to guess a better error message using common API error formats
|
||||
// The MIME type isn't explicitly checked because some formats inherit from others
|
||||
// Ex: JSON:API follows RFC 7807 semantics, Hydra can be used in any JSON-LD-compatible format
|
||||
if ($isJson && $body = json_decode($response->getContent(false), true)) {
|
||||
if (isset($body['hydra:title']) || isset($body['hydra:description'])) {
|
||||
// see http://www.hydra-cg.com/spec/latest/core/#description-of-http-status-codes-and-errors
|
||||
$separator = isset($body['hydra:title'], $body['hydra:description']) ? "\n\n" : '';
|
||||
$message = ($body['hydra:title'] ?? '').$separator.($body['hydra:description'] ?? '');
|
||||
} elseif ((isset($body['title']) || isset($body['detail']))
|
||||
&& (\is_scalar($body['title'] ?? '') && \is_scalar($body['detail'] ?? ''))) {
|
||||
// see RFC 7807 and https://jsonapi.org/format/#error-objects
|
||||
$separator = isset($body['title'], $body['detail']) ? "\n\n" : '';
|
||||
$message = ($body['title'] ?? '').$separator.($body['detail'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class InvalidArgumentException extends \InvalidArgumentException implements TransportExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
|
||||
/**
|
||||
* Thrown by responses' toArray() method when their content cannot be JSON-decoded.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class JsonException extends \JsonException implements DecodingExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
|
||||
/**
|
||||
* Represents a 3xx response.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class RedirectionException extends \RuntimeException implements RedirectionExceptionInterface
|
||||
{
|
||||
use HttpExceptionTrait;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
|
||||
/**
|
||||
* Represents a 5xx response.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class ServerException extends \RuntimeException implements ServerExceptionInterface
|
||||
{
|
||||
use HttpExceptionTrait;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TimeoutExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class TimeoutException extends TransportException implements TimeoutExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class TransportException extends \RuntimeException implements TransportExceptionInterface
|
||||
{
|
||||
}
|
||||
79
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttpClient.php
vendored
Normal file
79
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttpClient.php
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use Amp\Http\Client\Connection\ConnectionLimitingPool;
|
||||
use Amp\Promise;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* A factory to instantiate the best possible HTTP client for the runtime.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class HttpClient
|
||||
{
|
||||
/**
|
||||
* @param array $defaultOptions Default request's options
|
||||
* @param int $maxHostConnections The maximum number of connections to a single host
|
||||
* @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue
|
||||
*
|
||||
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
|
||||
*/
|
||||
public static function create(array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface
|
||||
{
|
||||
if ($amp = class_exists(ConnectionLimitingPool::class) && interface_exists(Promise::class)) {
|
||||
if (!\extension_loaded('curl')) {
|
||||
return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes);
|
||||
}
|
||||
|
||||
// Skip curl when HTTP/2 push is unsupported or buggy, see https://bugs.php.net/77535
|
||||
if (\PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304) || !\defined('CURLMOPT_PUSHFUNCTION')) {
|
||||
return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes);
|
||||
}
|
||||
|
||||
static $curlVersion = null;
|
||||
$curlVersion = $curlVersion ?? curl_version();
|
||||
|
||||
// HTTP/2 push crashes before curl 7.61
|
||||
if (0x073D00 > $curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & $curlVersion['features'])) {
|
||||
return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes);
|
||||
}
|
||||
}
|
||||
|
||||
if (\extension_loaded('curl')) {
|
||||
if ('\\' !== \DIRECTORY_SEPARATOR || isset($defaultOptions['cafile']) || isset($defaultOptions['capath']) || \ini_get('curl.cainfo') || \ini_get('openssl.cafile') || \ini_get('openssl.capath')) {
|
||||
return new CurlHttpClient($defaultOptions, $maxHostConnections, $maxPendingPushes);
|
||||
}
|
||||
|
||||
@trigger_error('Configure the "curl.cainfo", "openssl.cafile" or "openssl.capath" php.ini setting to enable the CurlHttpClient', \E_USER_WARNING);
|
||||
}
|
||||
|
||||
if ($amp) {
|
||||
return new AmpHttpClient($defaultOptions, null, $maxHostConnections, $maxPendingPushes);
|
||||
}
|
||||
|
||||
@trigger_error((\extension_loaded('curl') ? 'Upgrade' : 'Install').' the curl extension or run "composer require amphp/http-client:^4.2.1" to perform async HTTP operations, including full HTTP/2 support', \E_USER_NOTICE);
|
||||
|
||||
return new NativeHttpClient($defaultOptions, $maxHostConnections);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a client that adds options (e.g. authentication headers) only when the request URL matches the provided base URI.
|
||||
*/
|
||||
public static function createForBaseUri(string $baseUri, array $defaultOptions = [], int $maxHostConnections = 6, int $maxPendingPushes = 50): HttpClientInterface
|
||||
{
|
||||
$client = self::create([], $maxHostConnections, $maxPendingPushes);
|
||||
|
||||
return ScopingHttpClient::forBaseUri($client, $baseUri, $defaultOptions);
|
||||
}
|
||||
}
|
||||
739
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttpClientTrait.php
vendored
Normal file
739
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttpClientTrait.php
vendored
Normal file
@@ -0,0 +1,739 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* Provides the common logic from writing HttpClientInterface implementations.
|
||||
*
|
||||
* All private methods are static to prevent implementers from creating memory leaks via circular references.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
trait HttpClientTrait
|
||||
{
|
||||
private static $CHUNK_SIZE = 16372;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function withOptions(array $options): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and normalizes method, URL and options, and merges them with defaults.
|
||||
*
|
||||
* @throws InvalidArgumentException When a not-supported option is found
|
||||
*/
|
||||
private static function prepareRequest(?string $method, ?string $url, array $options, array $defaultOptions = [], bool $allowExtraOptions = false): array
|
||||
{
|
||||
if (null !== $method) {
|
||||
if (\strlen($method) !== strspn($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid HTTP method "%s", only uppercase letters are accepted.', $method));
|
||||
}
|
||||
if (!$method) {
|
||||
throw new InvalidArgumentException('The HTTP method cannot be empty.');
|
||||
}
|
||||
}
|
||||
|
||||
$options = self::mergeDefaultOptions($options, $defaultOptions, $allowExtraOptions);
|
||||
|
||||
$buffer = $options['buffer'] ?? true;
|
||||
|
||||
if ($buffer instanceof \Closure) {
|
||||
$options['buffer'] = static function (array $headers) use ($buffer) {
|
||||
if (!\is_bool($buffer = $buffer($headers))) {
|
||||
if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) {
|
||||
throw new \LogicException(sprintf('The closure passed as option "buffer" must return bool or stream resource, got "%s".', get_debug_type($buffer)));
|
||||
}
|
||||
|
||||
if (false === strpbrk($bufferInfo['mode'], 'acew+')) {
|
||||
throw new \LogicException(sprintf('The stream returned by the closure passed as option "buffer" must be writeable, got mode "%s".', $bufferInfo['mode']));
|
||||
}
|
||||
}
|
||||
|
||||
return $buffer;
|
||||
};
|
||||
} elseif (!\is_bool($buffer)) {
|
||||
if (!\is_array($bufferInfo = @stream_get_meta_data($buffer))) {
|
||||
throw new InvalidArgumentException(sprintf('Option "buffer" must be bool, stream resource or Closure, "%s" given.', get_debug_type($buffer)));
|
||||
}
|
||||
|
||||
if (false === strpbrk($bufferInfo['mode'], 'acew+')) {
|
||||
throw new InvalidArgumentException(sprintf('The stream in option "buffer" must be writeable, mode "%s" given.', $bufferInfo['mode']));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['json'])) {
|
||||
if (isset($options['body']) && '' !== $options['body']) {
|
||||
throw new InvalidArgumentException('Define either the "json" or the "body" option, setting both is not supported.');
|
||||
}
|
||||
$options['body'] = self::jsonEncode($options['json']);
|
||||
unset($options['json']);
|
||||
|
||||
if (!isset($options['normalized_headers']['content-type'])) {
|
||||
$options['normalized_headers']['content-type'] = ['Content-Type: application/json'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($options['normalized_headers']['accept'])) {
|
||||
$options['normalized_headers']['accept'] = ['Accept: */*'];
|
||||
}
|
||||
|
||||
if (isset($options['body'])) {
|
||||
$options['body'] = self::normalizeBody($options['body']);
|
||||
|
||||
if (\is_string($options['body'])
|
||||
&& (string) \strlen($options['body']) !== substr($h = $options['normalized_headers']['content-length'][0] ?? '', 16)
|
||||
&& ('' !== $h || '' !== $options['body'])
|
||||
) {
|
||||
if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) {
|
||||
unset($options['normalized_headers']['transfer-encoding']);
|
||||
$options['body'] = self::dechunk($options['body']);
|
||||
}
|
||||
|
||||
$options['normalized_headers']['content-length'] = [substr_replace($h ?: 'Content-Length: ', \strlen($options['body']), 16)];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['peer_fingerprint'])) {
|
||||
$options['peer_fingerprint'] = self::normalizePeerFingerprint($options['peer_fingerprint']);
|
||||
}
|
||||
|
||||
// Validate on_progress
|
||||
if (isset($options['on_progress']) && !\is_callable($onProgress = $options['on_progress'])) {
|
||||
throw new InvalidArgumentException(sprintf('Option "on_progress" must be callable, "%s" given.', get_debug_type($onProgress)));
|
||||
}
|
||||
|
||||
if (\is_array($options['auth_basic'] ?? null)) {
|
||||
$count = \count($options['auth_basic']);
|
||||
if ($count <= 0 || $count > 2) {
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_basic" must contain 1 or 2 elements, "%s" given.', $count));
|
||||
}
|
||||
|
||||
$options['auth_basic'] = implode(':', $options['auth_basic']);
|
||||
}
|
||||
|
||||
if (!\is_string($options['auth_basic'] ?? '')) {
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_basic" must be string or an array, "%s" given.', get_debug_type($options['auth_basic'])));
|
||||
}
|
||||
|
||||
if (isset($options['auth_bearer'])) {
|
||||
if (!\is_string($options['auth_bearer'])) {
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_bearer" must be a string, "%s" given.', get_debug_type($options['auth_bearer'])));
|
||||
}
|
||||
if (preg_match('{[^\x21-\x7E]}', $options['auth_bearer'])) {
|
||||
throw new InvalidArgumentException('Invalid character found in option "auth_bearer": '.json_encode($options['auth_bearer']).'.');
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['auth_basic'], $options['auth_bearer'])) {
|
||||
throw new InvalidArgumentException('Define either the "auth_basic" or the "auth_bearer" option, setting both is not supported.');
|
||||
}
|
||||
|
||||
if (null !== $url) {
|
||||
// Merge auth with headers
|
||||
if (($options['auth_basic'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) {
|
||||
$options['normalized_headers']['authorization'] = ['Authorization: Basic '.base64_encode($options['auth_basic'])];
|
||||
}
|
||||
// Merge bearer with headers
|
||||
if (($options['auth_bearer'] ?? false) && !($options['normalized_headers']['authorization'] ?? false)) {
|
||||
$options['normalized_headers']['authorization'] = ['Authorization: Bearer '.$options['auth_bearer']];
|
||||
}
|
||||
|
||||
unset($options['auth_basic'], $options['auth_bearer']);
|
||||
|
||||
// Parse base URI
|
||||
if (\is_string($options['base_uri'])) {
|
||||
$options['base_uri'] = self::parseUrl($options['base_uri']);
|
||||
}
|
||||
|
||||
// Validate and resolve URL
|
||||
$url = self::parseUrl($url, $options['query']);
|
||||
$url = self::resolveUrl($url, $options['base_uri'], $defaultOptions['query'] ?? []);
|
||||
}
|
||||
|
||||
// Finalize normalization of options
|
||||
$options['http_version'] = (string) ($options['http_version'] ?? '') ?: null;
|
||||
if (0 > $options['timeout'] = (float) ($options['timeout'] ?? \ini_get('default_socket_timeout'))) {
|
||||
$options['timeout'] = 172800.0; // 2 days
|
||||
}
|
||||
|
||||
$options['max_duration'] = isset($options['max_duration']) ? (float) $options['max_duration'] : 0;
|
||||
$options['headers'] = array_merge(...array_values($options['normalized_headers']));
|
||||
|
||||
return [$url, $options];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidArgumentException When an invalid option is found
|
||||
*/
|
||||
private static function mergeDefaultOptions(array $options, array $defaultOptions, bool $allowExtraOptions = false): array
|
||||
{
|
||||
$options['normalized_headers'] = self::normalizeHeaders($options['headers'] ?? []);
|
||||
|
||||
if ($defaultOptions['headers'] ?? false) {
|
||||
$options['normalized_headers'] += self::normalizeHeaders($defaultOptions['headers']);
|
||||
}
|
||||
|
||||
$options['headers'] = array_merge(...array_values($options['normalized_headers']) ?: [[]]);
|
||||
|
||||
if ($resolve = $options['resolve'] ?? false) {
|
||||
$options['resolve'] = [];
|
||||
foreach ($resolve as $k => $v) {
|
||||
if ('' === $v = (string) $v) {
|
||||
$v = null;
|
||||
} elseif ('[' === $v[0] && ']' === substr($v, -1) && str_contains($v, ':')) {
|
||||
$v = substr($v, 1, -1);
|
||||
}
|
||||
|
||||
$options['resolve'][substr(self::parseUrl('http://'.$k)['authority'], 2)] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
// Option "query" is never inherited from defaults
|
||||
$options['query'] = $options['query'] ?? [];
|
||||
|
||||
$options += $defaultOptions;
|
||||
|
||||
if (isset(self::$emptyDefaults)) {
|
||||
foreach (self::$emptyDefaults as $k => $v) {
|
||||
if (!isset($options[$k])) {
|
||||
$options[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($defaultOptions['extra'])) {
|
||||
$options['extra'] += $defaultOptions['extra'];
|
||||
}
|
||||
|
||||
if ($resolve = $defaultOptions['resolve'] ?? false) {
|
||||
foreach ($resolve as $k => $v) {
|
||||
if ('' === $v = (string) $v) {
|
||||
$v = null;
|
||||
} elseif ('[' === $v[0] && ']' === substr($v, -1) && str_contains($v, ':')) {
|
||||
$v = substr($v, 1, -1);
|
||||
}
|
||||
|
||||
$options['resolve'] += [substr(self::parseUrl('http://'.$k)['authority'], 2) => $v];
|
||||
}
|
||||
}
|
||||
|
||||
if ($allowExtraOptions || !$defaultOptions) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
// Look for unsupported options
|
||||
foreach ($options as $name => $v) {
|
||||
if (\array_key_exists($name, $defaultOptions) || 'normalized_headers' === $name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('auth_ntlm' === $name) {
|
||||
if (!\extension_loaded('curl')) {
|
||||
$msg = 'try installing the "curl" extension to use "%s" instead.';
|
||||
} else {
|
||||
$msg = 'try using "%s" instead.';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Option "auth_ntlm" is not supported by "%s", '.$msg, __CLASS__, CurlHttpClient::class));
|
||||
}
|
||||
|
||||
$alternatives = [];
|
||||
|
||||
foreach ($defaultOptions as $k => $v) {
|
||||
if (levenshtein($name, $k) <= \strlen($name) / 3 || str_contains($k, $name)) {
|
||||
$alternatives[] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Unsupported option "%s" passed to "%s", did you mean "%s"?', $name, __CLASS__, implode('", "', $alternatives ?: array_keys($defaultOptions))));
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[][]
|
||||
*
|
||||
* @throws InvalidArgumentException When an invalid header is found
|
||||
*/
|
||||
private static function normalizeHeaders(array $headers): array
|
||||
{
|
||||
$normalizedHeaders = [];
|
||||
|
||||
foreach ($headers as $name => $values) {
|
||||
if (\is_object($values) && method_exists($values, '__toString')) {
|
||||
$values = (string) $values;
|
||||
}
|
||||
|
||||
if (\is_int($name)) {
|
||||
if (!\is_string($values)) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values)));
|
||||
}
|
||||
[$name, $values] = explode(':', $values, 2);
|
||||
$values = [ltrim($values)];
|
||||
} elseif (!is_iterable($values)) {
|
||||
if (\is_object($values)) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid value for header "%s": expected string, "%s" given.', $name, get_debug_type($values)));
|
||||
}
|
||||
|
||||
$values = (array) $values;
|
||||
}
|
||||
|
||||
$lcName = strtolower($name);
|
||||
$normalizedHeaders[$lcName] = [];
|
||||
|
||||
foreach ($values as $value) {
|
||||
$normalizedHeaders[$lcName][] = $value = $name.': '.$value;
|
||||
|
||||
if (\strlen($value) !== strcspn($value, "\r\n\0")) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid header: CR/LF/NUL found in "%s".', $value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $normalizedHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|resource|\Traversable|\Closure $body
|
||||
*
|
||||
* @return string|resource|\Closure
|
||||
*
|
||||
* @throws InvalidArgumentException When an invalid body is passed
|
||||
*/
|
||||
private static function normalizeBody($body)
|
||||
{
|
||||
if (\is_array($body)) {
|
||||
array_walk_recursive($body, $caster = static function (&$v) use (&$caster) {
|
||||
if (\is_object($v)) {
|
||||
if ($vars = get_object_vars($v)) {
|
||||
array_walk_recursive($vars, $caster);
|
||||
$v = $vars;
|
||||
} elseif (method_exists($v, '__toString')) {
|
||||
$v = (string) $v;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return http_build_query($body, '', '&');
|
||||
}
|
||||
|
||||
if (\is_string($body)) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$generatorToCallable = static function (\Generator $body): \Closure {
|
||||
return static function () use ($body) {
|
||||
while ($body->valid()) {
|
||||
$chunk = $body->current();
|
||||
$body->next();
|
||||
|
||||
if ('' !== $chunk) {
|
||||
return $chunk;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
};
|
||||
|
||||
if ($body instanceof \Generator) {
|
||||
return $generatorToCallable($body);
|
||||
}
|
||||
|
||||
if ($body instanceof \Traversable) {
|
||||
return $generatorToCallable((static function ($body) { yield from $body; })($body));
|
||||
}
|
||||
|
||||
if ($body instanceof \Closure) {
|
||||
$r = new \ReflectionFunction($body);
|
||||
$body = $r->getClosure();
|
||||
|
||||
if ($r->isGenerator()) {
|
||||
$body = $body(self::$CHUNK_SIZE);
|
||||
|
||||
return $generatorToCallable($body);
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
if (!\is_array(@stream_get_meta_data($body))) {
|
||||
throw new InvalidArgumentException(sprintf('Option "body" must be string, stream resource, iterable or callable, "%s" given.', get_debug_type($body)));
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
private static function dechunk(string $body): string
|
||||
{
|
||||
$h = fopen('php://temp', 'w+');
|
||||
stream_filter_append($h, 'dechunk', \STREAM_FILTER_WRITE);
|
||||
fwrite($h, $body);
|
||||
$body = stream_get_contents($h, -1, 0);
|
||||
rewind($h);
|
||||
ftruncate($h, 0);
|
||||
|
||||
if (fwrite($h, '-') && '' !== stream_get_contents($h, -1, 0)) {
|
||||
throw new TransportException('Request body has broken chunked encoding.');
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|string[] $fingerprint
|
||||
*
|
||||
* @throws InvalidArgumentException When an invalid fingerprint is passed
|
||||
*/
|
||||
private static function normalizePeerFingerprint($fingerprint): array
|
||||
{
|
||||
if (\is_string($fingerprint)) {
|
||||
switch (\strlen($fingerprint = str_replace(':', '', $fingerprint))) {
|
||||
case 32: $fingerprint = ['md5' => $fingerprint]; break;
|
||||
case 40: $fingerprint = ['sha1' => $fingerprint]; break;
|
||||
case 44: $fingerprint = ['pin-sha256' => [$fingerprint]]; break;
|
||||
case 64: $fingerprint = ['sha256' => $fingerprint]; break;
|
||||
default: throw new InvalidArgumentException(sprintf('Cannot auto-detect fingerprint algorithm for "%s".', $fingerprint));
|
||||
}
|
||||
} elseif (\is_array($fingerprint)) {
|
||||
foreach ($fingerprint as $algo => $hash) {
|
||||
$fingerprint[$algo] = 'pin-sha256' === $algo ? (array) $hash : str_replace(':', '', $hash);
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException(sprintf('Option "peer_fingerprint" must be string or array, "%s" given.', get_debug_type($fingerprint)));
|
||||
}
|
||||
|
||||
return $fingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws InvalidArgumentException When the value cannot be json-encoded
|
||||
*/
|
||||
private static function jsonEncode($value, ?int $flags = null, int $maxDepth = 512): string
|
||||
{
|
||||
$flags = $flags ?? (\JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION);
|
||||
|
||||
try {
|
||||
$value = json_encode($value, $flags | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0), $maxDepth);
|
||||
} catch (\JsonException $e) {
|
||||
throw new InvalidArgumentException('Invalid value for "json" option: '.$e->getMessage());
|
||||
}
|
||||
|
||||
if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error() && (false === $value || !($flags & \JSON_PARTIAL_OUTPUT_ON_ERROR))) {
|
||||
throw new InvalidArgumentException('Invalid value for "json" option: '.json_last_error_msg());
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a URL against a base URI.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc3986#section-5.2.2
|
||||
*
|
||||
* @throws InvalidArgumentException When an invalid URL is passed
|
||||
*/
|
||||
private static function resolveUrl(array $url, ?array $base, array $queryDefaults = []): array
|
||||
{
|
||||
$givenUrl = $url;
|
||||
|
||||
if (null !== $base && '' === ($base['scheme'] ?? '').($base['authority'] ?? '')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid "base_uri" option: host or scheme is missing in "%s".', implode('', $base)));
|
||||
}
|
||||
|
||||
if (null === $url['scheme'] && (null === $base || null === $base['scheme'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid URL: scheme is missing in "%s". Did you forget to add "http(s)://"?', implode('', $base ?? $url)));
|
||||
}
|
||||
|
||||
if (null === $base && '' === $url['scheme'].$url['authority']) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid URL: no "base_uri" option was provided and host or scheme is missing in "%s".', implode('', $url)));
|
||||
}
|
||||
|
||||
if (null !== $url['scheme']) {
|
||||
$url['path'] = self::removeDotSegments($url['path'] ?? '');
|
||||
} else {
|
||||
if (null !== $url['authority']) {
|
||||
$url['path'] = self::removeDotSegments($url['path'] ?? '');
|
||||
} else {
|
||||
if (null === $url['path']) {
|
||||
$url['path'] = $base['path'];
|
||||
$url['query'] = $url['query'] ?? $base['query'];
|
||||
} else {
|
||||
if ('/' !== $url['path'][0]) {
|
||||
if (null === $base['path']) {
|
||||
$url['path'] = '/'.$url['path'];
|
||||
} else {
|
||||
$segments = explode('/', $base['path']);
|
||||
array_splice($segments, -1, 1, [$url['path']]);
|
||||
$url['path'] = implode('/', $segments);
|
||||
}
|
||||
}
|
||||
|
||||
$url['path'] = self::removeDotSegments($url['path']);
|
||||
}
|
||||
|
||||
$url['authority'] = $base['authority'];
|
||||
|
||||
if ($queryDefaults) {
|
||||
$url['query'] = '?'.self::mergeQueryString(substr($url['query'] ?? '', 1), $queryDefaults, false);
|
||||
}
|
||||
}
|
||||
|
||||
$url['scheme'] = $base['scheme'];
|
||||
}
|
||||
|
||||
if ('' === ($url['path'] ?? '')) {
|
||||
$url['path'] = '/';
|
||||
}
|
||||
|
||||
if ('?' === ($url['query'] ?? '')) {
|
||||
$url['query'] = null;
|
||||
}
|
||||
|
||||
if (null !== $url['scheme'] && null === $url['authority']) {
|
||||
throw new InvalidArgumentException(\sprintf('Invalid URL: host is missing in "%s".', implode('', $givenUrl)));
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a URL and fixes its encoding if needed.
|
||||
*
|
||||
* @throws InvalidArgumentException When an invalid URL is passed
|
||||
*/
|
||||
private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array
|
||||
{
|
||||
$tail = '';
|
||||
|
||||
if (false === $parts = parse_url(\strlen($url) !== strcspn($url, '?#') ? $url : $url.$tail = '#')) {
|
||||
throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url));
|
||||
}
|
||||
|
||||
if ($query) {
|
||||
$parts['query'] = self::mergeQueryString($parts['query'] ?? null, $query, true);
|
||||
}
|
||||
|
||||
$scheme = $parts['scheme'] ?? null;
|
||||
$host = $parts['host'] ?? null;
|
||||
|
||||
if (!$scheme && $host && !str_starts_with($url, '//')) {
|
||||
$parts = parse_url(':/'.$url.$tail);
|
||||
$parts['path'] = substr($parts['path'], 2);
|
||||
$scheme = $host = null;
|
||||
}
|
||||
|
||||
$port = $parts['port'] ?? 0;
|
||||
|
||||
if (null !== $scheme) {
|
||||
if (!isset($allowedSchemes[$scheme = strtolower($scheme)])) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported scheme in "%s": "%s" expected.', $url, implode('" or "', array_keys($allowedSchemes))));
|
||||
}
|
||||
|
||||
$port = $allowedSchemes[$scheme] === $port ? 0 : $port;
|
||||
$scheme .= ':';
|
||||
}
|
||||
|
||||
if (null !== $host) {
|
||||
if (!\defined('INTL_IDNA_VARIANT_UTS46') && preg_match('/[\x80-\xFF]/', $host)) {
|
||||
throw new InvalidArgumentException(sprintf('Unsupported IDN "%s", try enabling the "intl" PHP extension or running "composer require symfony/polyfill-intl-idn".', $host));
|
||||
}
|
||||
|
||||
$host = \defined('INTL_IDNA_VARIANT_UTS46') ? idn_to_ascii($host, \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46) ?: strtolower($host) : strtolower($host);
|
||||
$host .= $port ? ':'.$port : '';
|
||||
}
|
||||
|
||||
foreach (['user', 'pass', 'path', 'query', 'fragment'] as $part) {
|
||||
if (!isset($parts[$part])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains($parts[$part], '%')) {
|
||||
// https://tools.ietf.org/html/rfc3986#section-2.3
|
||||
$parts[$part] = preg_replace_callback('/%(?:2[DE]|3[0-9]|[46][1-9A-F]|5F|[57][0-9A]|7E)++/i', function ($m) { return rawurldecode($m[0]); }, $parts[$part]);
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc3986#section-3.3
|
||||
$parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@{}%]++#", function ($m) { return rawurlencode($m[0]); }, $parts[$part]);
|
||||
}
|
||||
|
||||
return [
|
||||
'scheme' => $scheme,
|
||||
'authority' => null !== $host ? '//'.(isset($parts['user']) ? $parts['user'].(isset($parts['pass']) ? ':'.$parts['pass'] : '').'@' : '').$host : null,
|
||||
'path' => isset($parts['path'][0]) ? $parts['path'] : null,
|
||||
'query' => isset($parts['query']) ? '?'.$parts['query'] : null,
|
||||
'fragment' => isset($parts['fragment']) && !$tail ? '#'.$parts['fragment'] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes dot-segments from a path.
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc3986#section-5.2.4
|
||||
*/
|
||||
private static function removeDotSegments(string $path)
|
||||
{
|
||||
$result = '';
|
||||
|
||||
while (!\in_array($path, ['', '.', '..'], true)) {
|
||||
if ('.' === $path[0] && (str_starts_with($path, $p = '../') || str_starts_with($path, $p = './'))) {
|
||||
$path = substr($path, \strlen($p));
|
||||
} elseif ('/.' === $path || str_starts_with($path, '/./')) {
|
||||
$path = substr_replace($path, '/', 0, 3);
|
||||
} elseif ('/..' === $path || str_starts_with($path, '/../')) {
|
||||
$i = strrpos($result, '/');
|
||||
$result = $i ? substr($result, 0, $i) : '';
|
||||
$path = substr_replace($path, '/', 0, 4);
|
||||
} else {
|
||||
$i = strpos($path, '/', 1) ?: \strlen($path);
|
||||
$result .= substr($path, 0, $i);
|
||||
$path = substr($path, $i);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges and encodes a query array with a query string.
|
||||
*
|
||||
* @throws InvalidArgumentException When an invalid query-string value is passed
|
||||
*/
|
||||
private static function mergeQueryString(?string $queryString, array $queryArray, bool $replace): ?string
|
||||
{
|
||||
if (!$queryArray) {
|
||||
return $queryString;
|
||||
}
|
||||
|
||||
$query = [];
|
||||
|
||||
if (null !== $queryString) {
|
||||
foreach (explode('&', $queryString) as $v) {
|
||||
if ('' !== $v) {
|
||||
$k = urldecode(explode('=', $v, 2)[0]);
|
||||
$query[$k] = (isset($query[$k]) ? $query[$k].'&' : '').$v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($replace) {
|
||||
foreach ($queryArray as $k => $v) {
|
||||
if (null === $v) {
|
||||
unset($query[$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$queryString = http_build_query($queryArray, '', '&', \PHP_QUERY_RFC3986);
|
||||
$queryArray = [];
|
||||
|
||||
if ($queryString) {
|
||||
if (str_contains($queryString, '%')) {
|
||||
// https://tools.ietf.org/html/rfc3986#section-2.3 + some chars not encoded by browsers
|
||||
$queryString = strtr($queryString, [
|
||||
'%21' => '!',
|
||||
'%24' => '$',
|
||||
'%28' => '(',
|
||||
'%29' => ')',
|
||||
'%2A' => '*',
|
||||
'%2F' => '/',
|
||||
'%3A' => ':',
|
||||
'%3B' => ';',
|
||||
'%40' => '@',
|
||||
'%5B' => '[',
|
||||
'%5D' => ']',
|
||||
]);
|
||||
}
|
||||
|
||||
foreach (explode('&', $queryString) as $v) {
|
||||
$queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return implode('&', $replace ? array_replace($query, $queryArray) : ($query + $queryArray));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads proxy configuration from the same environment variables as curl when no proxy is explicitly set.
|
||||
*/
|
||||
private static function getProxy(?string $proxy, array $url, ?string $noProxy): ?array
|
||||
{
|
||||
if (null === $proxy = self::getProxyUrl($proxy, $url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$proxy = (parse_url($proxy) ?: []) + ['scheme' => 'http'];
|
||||
|
||||
if (!isset($proxy['host'])) {
|
||||
throw new TransportException('Invalid HTTP proxy: host is missing.');
|
||||
}
|
||||
|
||||
if ('http' === $proxy['scheme']) {
|
||||
$proxyUrl = 'tcp://'.$proxy['host'].':'.($proxy['port'] ?? '80');
|
||||
} elseif ('https' === $proxy['scheme']) {
|
||||
$proxyUrl = 'ssl://'.$proxy['host'].':'.($proxy['port'] ?? '443');
|
||||
} else {
|
||||
throw new TransportException(sprintf('Unsupported proxy scheme "%s": "http" or "https" expected.', $proxy['scheme']));
|
||||
}
|
||||
|
||||
$noProxy = $noProxy ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '';
|
||||
$noProxy = $noProxy ? preg_split('/[\s,]+/', $noProxy) : [];
|
||||
|
||||
return [
|
||||
'url' => $proxyUrl,
|
||||
'auth' => isset($proxy['user']) ? 'Basic '.base64_encode(rawurldecode($proxy['user']).':'.rawurldecode($proxy['pass'] ?? '')) : null,
|
||||
'no_proxy' => $noProxy,
|
||||
];
|
||||
}
|
||||
|
||||
private static function getProxyUrl(?string $proxy, array $url): ?string
|
||||
{
|
||||
if (null !== $proxy) {
|
||||
return $proxy;
|
||||
}
|
||||
|
||||
// Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities
|
||||
$proxy = $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null;
|
||||
|
||||
if ('https:' === $url['scheme']) {
|
||||
$proxy = $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? $proxy;
|
||||
}
|
||||
|
||||
return $proxy;
|
||||
}
|
||||
|
||||
private static function shouldBuffer(array $headers): bool
|
||||
{
|
||||
if (null === $contentType = $headers['content-type'][0] ?? null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false !== $i = strpos($contentType, ';')) {
|
||||
$contentType = substr($contentType, 0, $i);
|
||||
}
|
||||
|
||||
return $contentType && preg_match('#^(?:text/|application/(?:.+\+)?(?:json|xml)$)#i', $contentType);
|
||||
}
|
||||
}
|
||||
333
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttpOptions.php
vendored
Normal file
333
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttpOptions.php
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* A helper providing autocompletion for available options.
|
||||
*
|
||||
* @see HttpClientInterface for a description of each options.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class HttpOptions
|
||||
{
|
||||
private $options = [];
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setAuthBasic(string $user, string $password = '')
|
||||
{
|
||||
$this->options['auth_basic'] = $user;
|
||||
|
||||
if ('' !== $password) {
|
||||
$this->options['auth_basic'] .= ':'.$password;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setAuthBearer(string $token)
|
||||
{
|
||||
$this->options['auth_bearer'] = $token;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setQuery(array $query)
|
||||
{
|
||||
$this->options['query'] = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaders(iterable $headers)
|
||||
{
|
||||
$this->options['headers'] = $headers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string|resource|\Traversable|\Closure $body
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setBody($body)
|
||||
{
|
||||
$this->options['body'] = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $json
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setJson($json)
|
||||
{
|
||||
$this->options['json'] = $json;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setUserData($data)
|
||||
{
|
||||
$this->options['user_data'] = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxRedirects(int $max)
|
||||
{
|
||||
$this->options['max_redirects'] = $max;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHttpVersion(string $version)
|
||||
{
|
||||
$this->options['http_version'] = $version;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setBaseUri(string $uri)
|
||||
{
|
||||
$this->options['base_uri'] = $uri;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function buffer(bool $buffer)
|
||||
{
|
||||
$this->options['buffer'] = $buffer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable(int, int, array, \Closure|null=):void $callback
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setOnProgress(callable $callback)
|
||||
{
|
||||
$this->options['on_progress'] = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function resolve(array $hostIps)
|
||||
{
|
||||
$this->options['resolve'] = $hostIps;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setProxy(string $proxy)
|
||||
{
|
||||
$this->options['proxy'] = $proxy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setNoProxy(string $noProxy)
|
||||
{
|
||||
$this->options['no_proxy'] = $noProxy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setTimeout(float $timeout)
|
||||
{
|
||||
$this->options['timeout'] = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setMaxDuration(float $maxDuration)
|
||||
{
|
||||
$this->options['max_duration'] = $maxDuration;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function bindTo(string $bindto)
|
||||
{
|
||||
$this->options['bindto'] = $bindto;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function verifyPeer(bool $verify)
|
||||
{
|
||||
$this->options['verify_peer'] = $verify;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function verifyHost(bool $verify)
|
||||
{
|
||||
$this->options['verify_host'] = $verify;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setCaFile(string $cafile)
|
||||
{
|
||||
$this->options['cafile'] = $cafile;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setCaPath(string $capath)
|
||||
{
|
||||
$this->options['capath'] = $capath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setLocalCert(string $cert)
|
||||
{
|
||||
$this->options['local_cert'] = $cert;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setLocalPk(string $pk)
|
||||
{
|
||||
$this->options['local_pk'] = $pk;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setPassphrase(string $passphrase)
|
||||
{
|
||||
$this->options['passphrase'] = $passphrase;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setCiphers(string $ciphers)
|
||||
{
|
||||
$this->options['ciphers'] = $ciphers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $fingerprint
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setPeerFingerprint($fingerprint)
|
||||
{
|
||||
$this->options['peer_fingerprint'] = $fingerprint;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function capturePeerCertChain(bool $capture)
|
||||
{
|
||||
$this->options['capture_peer_cert_chain'] = $capture;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setExtra(string $name, $value)
|
||||
{
|
||||
$this->options['extra'][$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
276
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttplugClient.php
vendored
Normal file
276
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/HttplugClient.php
vendored
Normal file
@@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use GuzzleHttp\Promise\Promise as GuzzlePromise;
|
||||
use GuzzleHttp\Promise\RejectedPromise;
|
||||
use GuzzleHttp\Promise\Utils;
|
||||
use Http\Client\Exception\NetworkException;
|
||||
use Http\Client\Exception\RequestException;
|
||||
use Http\Client\HttpAsyncClient;
|
||||
use Http\Client\HttpClient as HttplugInterface;
|
||||
use Http\Discovery\Exception\NotFoundException;
|
||||
use Http\Discovery\Psr17FactoryDiscovery;
|
||||
use Http\Message\RequestFactory;
|
||||
use Http\Message\StreamFactory;
|
||||
use Http\Message\UriFactory;
|
||||
use Http\Promise\Promise;
|
||||
use KadenceWP\KadenceBlocks\Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use KadenceWP\KadenceBlocks\Nyholm\Psr7\Request;
|
||||
use KadenceWP\KadenceBlocks\Nyholm\Psr7\Uri;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\RequestFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\RequestInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\ResponseFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\StreamFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\StreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\UriFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\UriInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\HttplugWaitLoop;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\HttplugPromise;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
if (!interface_exists(HttplugInterface::class)) {
|
||||
throw new \LogicException('You cannot use "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\HttplugClient" as the "php-http/httplug" package is not installed. Try running "composer require php-http/httplug".');
|
||||
}
|
||||
|
||||
if (!interface_exists(RequestFactory::class)) {
|
||||
throw new \LogicException('You cannot use "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory".');
|
||||
}
|
||||
|
||||
/**
|
||||
* An adapter to turn a Symfony HttpClientInterface into an Httplug client.
|
||||
*
|
||||
* Run "composer require nyholm/psr7" to install an efficient implementation of response
|
||||
* and stream factories with flex-provided autowiring aliases.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactory, StreamFactory, UriFactory, ResetInterface
|
||||
{
|
||||
private $client;
|
||||
private $responseFactory;
|
||||
private $streamFactory;
|
||||
|
||||
/**
|
||||
* @var \SplObjectStorage<ResponseInterface, array{RequestInterface, Promise}>|null
|
||||
*/
|
||||
private $promisePool;
|
||||
|
||||
private $waitLoop;
|
||||
|
||||
public function __construct(?HttpClientInterface $client = null, ?ResponseFactoryInterface $responseFactory = null, ?StreamFactoryInterface $streamFactory = null)
|
||||
{
|
||||
$this->client = $client ?? HttpClient::create();
|
||||
$this->responseFactory = $responseFactory;
|
||||
$this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
|
||||
$this->promisePool = class_exists(Utils::class) ? new \SplObjectStorage() : null;
|
||||
|
||||
if (null === $this->responseFactory || null === $this->streamFactory) {
|
||||
if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) {
|
||||
throw new \LogicException('You cannot use the "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
|
||||
}
|
||||
|
||||
try {
|
||||
$psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
|
||||
$this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
|
||||
$this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
|
||||
} catch (NotFoundException $e) {
|
||||
throw new \LogicException('You cannot use the "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendRequest(RequestInterface $request): Psr7ResponseInterface
|
||||
{
|
||||
try {
|
||||
return HttplugWaitLoop::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $this->sendPsr7Request($request), true);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
throw new NetworkException($e->getMessage(), $request, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return HttplugPromise
|
||||
*/
|
||||
public function sendAsyncRequest(RequestInterface $request): Promise
|
||||
{
|
||||
if (!$promisePool = $this->promisePool) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s()" as the "guzzlehttp/promises" package is not installed. Try running "composer require guzzlehttp/promises".', __METHOD__));
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->sendPsr7Request($request, true);
|
||||
} catch (NetworkException $e) {
|
||||
return new HttplugPromise(new RejectedPromise($e));
|
||||
}
|
||||
|
||||
$waitLoop = $this->waitLoop;
|
||||
|
||||
$promise = new GuzzlePromise(static function () use ($response, $waitLoop) {
|
||||
$waitLoop->wait($response);
|
||||
}, static function () use ($response, $promisePool) {
|
||||
$response->cancel();
|
||||
unset($promisePool[$response]);
|
||||
});
|
||||
|
||||
$promisePool[$response] = [$request, $promise];
|
||||
|
||||
return new HttplugPromise($promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves pending promises that complete before the timeouts are reached.
|
||||
*
|
||||
* When $maxDuration is null and $idleTimeout is reached, promises are rejected.
|
||||
*
|
||||
* @return int The number of remaining pending promises
|
||||
*/
|
||||
public function wait(?float $maxDuration = null, ?float $idleTimeout = null): int
|
||||
{
|
||||
return $this->waitLoop->wait(null, $maxDuration, $idleTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
|
||||
{
|
||||
if ($this->responseFactory instanceof RequestFactoryInterface) {
|
||||
$request = $this->responseFactory->createRequest($method, $uri);
|
||||
} elseif (class_exists(Request::class)) {
|
||||
$request = new Request($method, $uri);
|
||||
} elseif (class_exists(Psr17FactoryDiscovery::class)) {
|
||||
$request = Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri);
|
||||
} else {
|
||||
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
|
||||
}
|
||||
|
||||
$request = $request
|
||||
->withProtocolVersion($protocolVersion)
|
||||
->withBody($this->createStream($body))
|
||||
;
|
||||
|
||||
foreach ($headers as $name => $value) {
|
||||
$request = $request->withAddedHeader($name, $value);
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createStream($body = null): StreamInterface
|
||||
{
|
||||
if ($body instanceof StreamInterface) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
if (\is_string($body ?? '')) {
|
||||
$stream = $this->streamFactory->createStream($body ?? '');
|
||||
} elseif (\is_resource($body)) {
|
||||
$stream = $this->streamFactory->createStreamFromResource($body);
|
||||
} else {
|
||||
throw new \InvalidArgumentException(sprintf('"%s()" expects string, resource or StreamInterface, "%s" given.', __METHOD__, get_debug_type($body)));
|
||||
}
|
||||
|
||||
if ($stream->isSeekable()) {
|
||||
$stream->seek(0);
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createUri($uri): UriInterface
|
||||
{
|
||||
if ($uri instanceof UriInterface) {
|
||||
return $uri;
|
||||
}
|
||||
|
||||
if ($this->responseFactory instanceof UriFactoryInterface) {
|
||||
return $this->responseFactory->createUri($uri);
|
||||
}
|
||||
|
||||
if (class_exists(Uri::class)) {
|
||||
return new Uri($uri);
|
||||
}
|
||||
|
||||
if (class_exists(Psr17FactoryDiscovery::class)) {
|
||||
return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri);
|
||||
}
|
||||
|
||||
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->wait();
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
|
||||
private function sendPsr7Request(RequestInterface $request, ?bool $buffer = null): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$body = $request->getBody();
|
||||
|
||||
if ($body->isSeekable()) {
|
||||
$body->seek(0);
|
||||
}
|
||||
|
||||
$options = [
|
||||
'headers' => $request->getHeaders(),
|
||||
'body' => $body->getContents(),
|
||||
'buffer' => $buffer,
|
||||
];
|
||||
|
||||
if ('1.0' === $request->getProtocolVersion()) {
|
||||
$options['http_version'] = '1.0';
|
||||
}
|
||||
|
||||
return $this->client->request($request->getMethod(), (string) $request->getUri(), $options);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new RequestException($e->getMessage(), $request, $e);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
throw new NetworkException($e->getMessage(), $request, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Internal/AmpBody.php
vendored
Normal file
142
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Internal/AmpBody.php
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
use Amp\ByteStream\InputStream;
|
||||
use Amp\ByteStream\ResourceInputStream;
|
||||
use Amp\Http\Client\RequestBody;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AmpBody implements RequestBody, InputStream
|
||||
{
|
||||
private $body;
|
||||
private $info;
|
||||
private $onProgress;
|
||||
private $offset = 0;
|
||||
private $length = -1;
|
||||
private $uploaded;
|
||||
|
||||
public function __construct($body, &$info, \Closure $onProgress)
|
||||
{
|
||||
$this->body = $body;
|
||||
$this->info = &$info;
|
||||
$this->onProgress = $onProgress;
|
||||
|
||||
if (\is_resource($body)) {
|
||||
$this->offset = ftell($body);
|
||||
$this->length = fstat($body)['size'];
|
||||
$this->body = new ResourceInputStream($body);
|
||||
} elseif (\is_string($body)) {
|
||||
$this->length = \strlen($body);
|
||||
}
|
||||
}
|
||||
|
||||
public function createBodyStream(): InputStream
|
||||
{
|
||||
if (null !== $this->uploaded) {
|
||||
$this->uploaded = null;
|
||||
|
||||
if (\is_string($this->body)) {
|
||||
$this->offset = 0;
|
||||
} elseif ($this->body instanceof ResourceInputStream) {
|
||||
fseek($this->body->getResource(), $this->offset);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHeaders(): Promise
|
||||
{
|
||||
return new Success([]);
|
||||
}
|
||||
|
||||
public function getBodyLength(): Promise
|
||||
{
|
||||
return new Success($this->length - $this->offset);
|
||||
}
|
||||
|
||||
public function read(): Promise
|
||||
{
|
||||
$this->info['size_upload'] += $this->uploaded;
|
||||
$this->uploaded = 0;
|
||||
($this->onProgress)();
|
||||
|
||||
$chunk = $this->doRead();
|
||||
$chunk->onResolve(function ($e, $data) {
|
||||
if (null !== $data) {
|
||||
$this->uploaded = \strlen($data);
|
||||
} else {
|
||||
$this->info['upload_content_length'] = $this->info['size_upload'];
|
||||
}
|
||||
});
|
||||
|
||||
return $chunk;
|
||||
}
|
||||
|
||||
public static function rewind(RequestBody $body): RequestBody
|
||||
{
|
||||
if (!$body instanceof self) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$body->uploaded = null;
|
||||
|
||||
if ($body->body instanceof ResourceInputStream) {
|
||||
fseek($body->body->getResource(), $body->offset);
|
||||
|
||||
return new $body($body->body, $body->info, $body->onProgress);
|
||||
}
|
||||
|
||||
if (\is_string($body->body)) {
|
||||
$body->offset = 0;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
private function doRead(): Promise
|
||||
{
|
||||
if ($this->body instanceof ResourceInputStream) {
|
||||
return $this->body->read();
|
||||
}
|
||||
|
||||
if (null === $this->offset || !$this->length) {
|
||||
return new Success();
|
||||
}
|
||||
|
||||
if (\is_string($this->body)) {
|
||||
$this->offset = null;
|
||||
|
||||
return new Success($this->body);
|
||||
}
|
||||
|
||||
if ('' === $data = ($this->body)(16372)) {
|
||||
$this->offset = null;
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
if (!\is_string($data)) {
|
||||
throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data)));
|
||||
}
|
||||
|
||||
return new Success($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
use Amp\CancellationToken;
|
||||
use Amp\Deferred;
|
||||
use Amp\Http\Client\Connection\ConnectionLimitingPool;
|
||||
use Amp\Http\Client\Connection\DefaultConnectionFactory;
|
||||
use Amp\Http\Client\InterceptedHttpClient;
|
||||
use Amp\Http\Client\Interceptor\RetryRequests;
|
||||
use Amp\Http\Client\PooledHttpClient;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Client\Response;
|
||||
use Amp\Http\Tunnel\Http1TunnelConnector;
|
||||
use Amp\Http\Tunnel\Https1TunnelConnector;
|
||||
use Amp\Promise;
|
||||
use Amp\Socket\Certificate;
|
||||
use Amp\Socket\ClientTlsContext;
|
||||
use Amp\Socket\ConnectContext;
|
||||
use Amp\Socket\Connector;
|
||||
use Amp\Socket\DnsConnector;
|
||||
use Amp\Socket\SocketAddress;
|
||||
use Amp\Success;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Internal representation of the Amp client's state.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AmpClientState extends ClientState
|
||||
{
|
||||
public $dnsCache = [];
|
||||
public $responseCount = 0;
|
||||
public $pushedResponses = [];
|
||||
|
||||
private $clients = [];
|
||||
private $clientConfigurator;
|
||||
private $maxHostConnections;
|
||||
private $maxPendingPushes;
|
||||
private $logger;
|
||||
|
||||
public function __construct(?callable $clientConfigurator, int $maxHostConnections, int $maxPendingPushes, ?LoggerInterface &$logger)
|
||||
{
|
||||
$this->clientConfigurator = $clientConfigurator ?? static function (PooledHttpClient $client) {
|
||||
return new InterceptedHttpClient($client, new RetryRequests(2));
|
||||
};
|
||||
$this->maxHostConnections = $maxHostConnections;
|
||||
$this->maxPendingPushes = $maxPendingPushes;
|
||||
$this->logger = &$logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Promise<Response>
|
||||
*/
|
||||
public function request(array $options, Request $request, CancellationToken $cancellation, array &$info, \Closure $onProgress, &$handle): Promise
|
||||
{
|
||||
if ($options['proxy']) {
|
||||
if ($request->hasHeader('proxy-authorization')) {
|
||||
$options['proxy']['auth'] = $request->getHeader('proxy-authorization');
|
||||
}
|
||||
|
||||
// Matching "no_proxy" should follow the behavior of curl
|
||||
$host = $request->getUri()->getHost();
|
||||
foreach ($options['proxy']['no_proxy'] as $rule) {
|
||||
$dotRule = '.'.ltrim($rule, '.');
|
||||
|
||||
if ('*' === $rule || $host === $rule || substr($host, -\strlen($dotRule)) === $dotRule) {
|
||||
$options['proxy'] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$request = clone $request;
|
||||
|
||||
if ($request->hasHeader('proxy-authorization')) {
|
||||
$request->removeHeader('proxy-authorization');
|
||||
}
|
||||
|
||||
if ($options['capture_peer_cert_chain']) {
|
||||
$info['peer_certificate_chain'] = [];
|
||||
}
|
||||
|
||||
$request->addEventListener(new AmpListener($info, $options['peer_fingerprint']['pin-sha256'] ?? [], $onProgress, $handle));
|
||||
$request->setPushHandler(function ($request, $response) use ($options): Promise {
|
||||
return $this->handlePush($request, $response, $options);
|
||||
});
|
||||
|
||||
($request->hasHeader('content-length') ? new Success((int) $request->getHeader('content-length')) : $request->getBody()->getBodyLength())
|
||||
->onResolve(static function ($e, $bodySize) use (&$info) {
|
||||
if (null !== $bodySize && 0 <= $bodySize) {
|
||||
$info['upload_content_length'] = ((1 + $info['upload_content_length']) ?? 1) - 1 + $bodySize;
|
||||
}
|
||||
});
|
||||
|
||||
[$client, $connector] = $this->getClient($options);
|
||||
$response = $client->request($request, $cancellation);
|
||||
$response->onResolve(static function ($e) use ($connector, &$handle) {
|
||||
if (null === $e) {
|
||||
$handle = $connector->handle;
|
||||
}
|
||||
});
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function getClient(array $options): array
|
||||
{
|
||||
$options = [
|
||||
'bindto' => $options['bindto'] ?: '0',
|
||||
'verify_peer' => $options['verify_peer'],
|
||||
'capath' => $options['capath'],
|
||||
'cafile' => $options['cafile'],
|
||||
'local_cert' => $options['local_cert'],
|
||||
'local_pk' => $options['local_pk'],
|
||||
'ciphers' => $options['ciphers'],
|
||||
'capture_peer_cert_chain' => $options['capture_peer_cert_chain'] || $options['peer_fingerprint'],
|
||||
'proxy' => $options['proxy'],
|
||||
];
|
||||
|
||||
$key = md5(serialize($options));
|
||||
|
||||
if (isset($this->clients[$key])) {
|
||||
return $this->clients[$key];
|
||||
}
|
||||
|
||||
$context = new ClientTlsContext('');
|
||||
$options['verify_peer'] || $context = $context->withoutPeerVerification();
|
||||
$options['cafile'] && $context = $context->withCaFile($options['cafile']);
|
||||
$options['capath'] && $context = $context->withCaPath($options['capath']);
|
||||
$options['local_cert'] && $context = $context->withCertificate(new Certificate($options['local_cert'], $options['local_pk']));
|
||||
$options['ciphers'] && $context = $context->withCiphers($options['ciphers']);
|
||||
$options['capture_peer_cert_chain'] && $context = $context->withPeerCapturing();
|
||||
|
||||
$connector = $handleConnector = new class() implements Connector {
|
||||
public $connector;
|
||||
public $uri;
|
||||
public $handle;
|
||||
|
||||
public function connect(string $uri, ?ConnectContext $context = null, ?CancellationToken $token = null): Promise
|
||||
{
|
||||
$result = $this->connector->connect($this->uri ?? $uri, $context, $token);
|
||||
$result->onResolve(function ($e, $socket) {
|
||||
$this->handle = null !== $socket ? $socket->getResource() : false;
|
||||
});
|
||||
|
||||
return $result;
|
||||
}
|
||||
};
|
||||
$connector->connector = new DnsConnector(new AmpResolver($this->dnsCache));
|
||||
|
||||
$context = (new ConnectContext())
|
||||
->withTcpNoDelay()
|
||||
->withTlsContext($context);
|
||||
|
||||
if ($options['bindto']) {
|
||||
if (file_exists($options['bindto'])) {
|
||||
$connector->uri = 'unix://'.$options['bindto'];
|
||||
} else {
|
||||
$context = $context->withBindTo($options['bindto']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($options['proxy']) {
|
||||
$proxyUrl = parse_url($options['proxy']['url']);
|
||||
$proxySocket = new SocketAddress($proxyUrl['host'], $proxyUrl['port']);
|
||||
$proxyHeaders = $options['proxy']['auth'] ? ['Proxy-Authorization' => $options['proxy']['auth']] : [];
|
||||
|
||||
if ('ssl' === $proxyUrl['scheme']) {
|
||||
$connector = new Https1TunnelConnector($proxySocket, $context->getTlsContext(), $proxyHeaders, $connector);
|
||||
} else {
|
||||
$connector = new Http1TunnelConnector($proxySocket, $proxyHeaders, $connector);
|
||||
}
|
||||
}
|
||||
|
||||
$maxHostConnections = 0 < $this->maxHostConnections ? $this->maxHostConnections : \PHP_INT_MAX;
|
||||
$pool = new DefaultConnectionFactory($connector, $context);
|
||||
$pool = ConnectionLimitingPool::byAuthority($maxHostConnections, $pool);
|
||||
|
||||
return $this->clients[$key] = [($this->clientConfigurator)(new PooledHttpClient($pool)), $handleConnector];
|
||||
}
|
||||
|
||||
private function handlePush(Request $request, Promise $response, array $options): Promise
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
$authority = $request->getUri()->getAuthority();
|
||||
|
||||
if ($this->maxPendingPushes <= \count($this->pushedResponses[$authority] ?? [])) {
|
||||
$fifoUrl = key($this->pushedResponses[$authority]);
|
||||
unset($this->pushedResponses[$authority][$fifoUrl]);
|
||||
$this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
|
||||
}
|
||||
|
||||
$url = (string) $request->getUri();
|
||||
$this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url));
|
||||
$this->pushedResponses[$authority][] = [$url, $deferred, $request, $response, [
|
||||
'proxy' => $options['proxy'],
|
||||
'bindto' => $options['bindto'],
|
||||
'local_cert' => $options['local_cert'],
|
||||
'local_pk' => $options['local_pk'],
|
||||
]];
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
use Amp\Http\Client\Connection\Stream;
|
||||
use Amp\Http\Client\EventListener;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AmpListener implements EventListener
|
||||
{
|
||||
private $info;
|
||||
private $pinSha256;
|
||||
private $onProgress;
|
||||
private $handle;
|
||||
|
||||
public function __construct(array &$info, array $pinSha256, \Closure $onProgress, &$handle)
|
||||
{
|
||||
$info += [
|
||||
'connect_time' => 0.0,
|
||||
'pretransfer_time' => 0.0,
|
||||
'starttransfer_time' => 0.0,
|
||||
'total_time' => 0.0,
|
||||
'namelookup_time' => 0.0,
|
||||
'primary_ip' => '',
|
||||
'primary_port' => 0,
|
||||
];
|
||||
|
||||
$this->info = &$info;
|
||||
$this->pinSha256 = $pinSha256;
|
||||
$this->onProgress = $onProgress;
|
||||
$this->handle = &$handle;
|
||||
}
|
||||
|
||||
public function startRequest(Request $request): Promise
|
||||
{
|
||||
$this->info['start_time'] = $this->info['start_time'] ?? microtime(true);
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function startDnsResolution(Request $request): Promise
|
||||
{
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function startConnectionCreation(Request $request): Promise
|
||||
{
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function startTlsNegotiation(Request $request): Promise
|
||||
{
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function startSendingRequest(Request $request, Stream $stream): Promise
|
||||
{
|
||||
$host = $stream->getRemoteAddress()->getHost();
|
||||
$this->info['primary_ip'] = $host;
|
||||
|
||||
if (false !== strpos($host, ':')) {
|
||||
$host = '['.$host.']';
|
||||
}
|
||||
|
||||
$this->info['primary_port'] = $stream->getRemoteAddress()->getPort();
|
||||
$this->info['pretransfer_time'] = microtime(true) - $this->info['start_time'];
|
||||
$this->info['debug'] .= sprintf("* Connected to %s (%s) port %d\n", $request->getUri()->getHost(), $host, $this->info['primary_port']);
|
||||
|
||||
if ((isset($this->info['peer_certificate_chain']) || $this->pinSha256) && null !== $tlsInfo = $stream->getTlsInfo()) {
|
||||
foreach ($tlsInfo->getPeerCertificates() as $cert) {
|
||||
$this->info['peer_certificate_chain'][] = openssl_x509_read($cert->toPem());
|
||||
}
|
||||
|
||||
if ($this->pinSha256) {
|
||||
$pin = openssl_pkey_get_public($this->info['peer_certificate_chain'][0]);
|
||||
$pin = openssl_pkey_get_details($pin)['key'];
|
||||
$pin = \array_slice(explode("\n", $pin), 1, -2);
|
||||
$pin = base64_decode(implode('', $pin));
|
||||
$pin = base64_encode(hash('sha256', $pin, true));
|
||||
|
||||
if (!\in_array($pin, $this->pinSha256, true)) {
|
||||
throw new TransportException(sprintf('SSL public key does not match pinned public key for "%s".', $this->info['url']));
|
||||
}
|
||||
}
|
||||
}
|
||||
($this->onProgress)();
|
||||
|
||||
$uri = $request->getUri();
|
||||
$requestUri = $uri->getPath() ?: '/';
|
||||
|
||||
if ('' !== $query = $uri->getQuery()) {
|
||||
$requestUri .= '?'.$query;
|
||||
}
|
||||
|
||||
if ('CONNECT' === $method = $request->getMethod()) {
|
||||
$requestUri = $uri->getHost().': '.($uri->getPort() ?? ('https' === $uri->getScheme() ? 443 : 80));
|
||||
}
|
||||
|
||||
$this->info['debug'] .= sprintf("> %s %s HTTP/%s \r\n", $method, $requestUri, $request->getProtocolVersions()[0]);
|
||||
|
||||
foreach ($request->getRawHeaders() as [$name, $value]) {
|
||||
$this->info['debug'] .= $name.': '.$value."\r\n";
|
||||
}
|
||||
$this->info['debug'] .= "\r\n";
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function completeSendingRequest(Request $request, Stream $stream): Promise
|
||||
{
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function startReceivingResponse(Request $request, Stream $stream): Promise
|
||||
{
|
||||
$this->info['starttransfer_time'] = microtime(true) - $this->info['start_time'];
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function completeReceivingResponse(Request $request, Stream $stream): Promise
|
||||
{
|
||||
$this->handle = null;
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function completeDnsResolution(Request $request): Promise
|
||||
{
|
||||
$this->info['namelookup_time'] = microtime(true) - $this->info['start_time'];
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function completeConnectionCreation(Request $request): Promise
|
||||
{
|
||||
$this->info['connect_time'] = microtime(true) - $this->info['start_time'];
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function completeTlsNegotiation(Request $request): Promise
|
||||
{
|
||||
($this->onProgress)();
|
||||
|
||||
return new Success();
|
||||
}
|
||||
|
||||
public function abort(Request $request, \Throwable $cause): Promise
|
||||
{
|
||||
return new Success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
use Amp\Dns;
|
||||
use Amp\Dns\Record;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
|
||||
/**
|
||||
* Handles local overrides for the DNS resolver.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AmpResolver implements Dns\Resolver
|
||||
{
|
||||
private $dnsMap;
|
||||
|
||||
public function __construct(array &$dnsMap)
|
||||
{
|
||||
$this->dnsMap = &$dnsMap;
|
||||
}
|
||||
|
||||
public function resolve(string $name, ?int $typeRestriction = null): Promise
|
||||
{
|
||||
$recordType = Record::A;
|
||||
$ip = $this->dnsMap[$name] ?? null;
|
||||
|
||||
if (null !== $ip && str_contains($ip, ':')) {
|
||||
$recordType = Record::AAAA;
|
||||
}
|
||||
if (null === $ip || $recordType !== ($typeRestriction ?? $recordType)) {
|
||||
return Dns\resolver()->resolve($name, $typeRestriction);
|
||||
}
|
||||
|
||||
return new Success([new Record($ip, $recordType, null)]);
|
||||
}
|
||||
|
||||
public function query(string $name, int $type): Promise
|
||||
{
|
||||
$recordType = Record::A;
|
||||
$ip = $this->dnsMap[$name] ?? null;
|
||||
|
||||
if (null !== $ip && str_contains($ip, ':')) {
|
||||
$recordType = Record::AAAA;
|
||||
}
|
||||
if (null === $ip || $recordType !== $type) {
|
||||
return Dns\resolver()->query($name, $type);
|
||||
}
|
||||
|
||||
return new Success([new Record($ip, $recordType, null)]);
|
||||
}
|
||||
}
|
||||
40
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Internal/Canary.php
vendored
Normal file
40
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Internal/Canary.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class Canary
|
||||
{
|
||||
private $canceller;
|
||||
|
||||
public function __construct(\Closure $canceller)
|
||||
{
|
||||
$this->canceller = $canceller;
|
||||
}
|
||||
|
||||
public function cancel()
|
||||
{
|
||||
if (($canceller = $this->canceller) instanceof \Closure) {
|
||||
$this->canceller = null;
|
||||
$canceller();
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
/**
|
||||
* Internal representation of the client state.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ClientState
|
||||
{
|
||||
public $handlesActivity = [];
|
||||
public $openHandles = [];
|
||||
public $lastTimeout;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\CurlResponse;
|
||||
|
||||
/**
|
||||
* Internal representation of the cURL client's state.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CurlClientState extends ClientState
|
||||
{
|
||||
/** @var \CurlMultiHandle|resource|null */
|
||||
public $handle;
|
||||
/** @var \CurlShareHandle|resource|null */
|
||||
public $share;
|
||||
/** @var PushedResponse[] */
|
||||
public $pushedResponses = [];
|
||||
/** @var DnsCache */
|
||||
public $dnsCache;
|
||||
/** @var float[] */
|
||||
public $pauseExpiries = [];
|
||||
public $execCounter = \PHP_INT_MIN;
|
||||
/** @var LoggerInterface|null */
|
||||
public $logger;
|
||||
public $performing = false;
|
||||
|
||||
public static $curlVersion;
|
||||
|
||||
public function __construct(int $maxHostConnections, int $maxPendingPushes)
|
||||
{
|
||||
self::$curlVersion = self::$curlVersion ?? curl_version();
|
||||
|
||||
$this->handle = curl_multi_init();
|
||||
$this->dnsCache = new DnsCache();
|
||||
$this->reset();
|
||||
|
||||
// Don't enable HTTP/1.1 pipelining: it forces responses to be sent in order
|
||||
if (\defined('CURLPIPE_MULTIPLEX')) {
|
||||
curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX);
|
||||
}
|
||||
if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS') && 0 < $maxHostConnections) {
|
||||
$maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, $maxHostConnections) ? 4294967295 : $maxHostConnections;
|
||||
}
|
||||
if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) {
|
||||
curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections);
|
||||
}
|
||||
|
||||
// Skip configuring HTTP/2 push when it's unsupported or buggy, see https://bugs.php.net/77535
|
||||
if (0 >= $maxPendingPushes || \PHP_VERSION_ID < 70217 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70304)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// HTTP/2 push crashes before curl 7.61
|
||||
if (!\defined('CURLMOPT_PUSHFUNCTION') || 0x073D00 > self::$curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & self::$curlVersion['features'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clone to prevent a circular reference
|
||||
$multi = clone $this;
|
||||
$multi->handle = null;
|
||||
$multi->share = null;
|
||||
$multi->pushedResponses = &$this->pushedResponses;
|
||||
$multi->logger = &$this->logger;
|
||||
$multi->handlesActivity = &$this->handlesActivity;
|
||||
$multi->openHandles = &$this->openHandles;
|
||||
|
||||
curl_multi_setopt($this->handle, \CURLMOPT_PUSHFUNCTION, static function ($parent, $pushed, array $requestHeaders) use ($multi, $maxPendingPushes) {
|
||||
return $multi->handlePush($parent, $pushed, $requestHeaders, $maxPendingPushes);
|
||||
});
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
foreach ($this->pushedResponses as $url => $response) {
|
||||
$this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
|
||||
curl_multi_remove_handle($this->handle, $response->handle);
|
||||
curl_close($response->handle);
|
||||
}
|
||||
|
||||
$this->pushedResponses = [];
|
||||
$this->dnsCache->evictions = $this->dnsCache->evictions ?: $this->dnsCache->removals;
|
||||
$this->dnsCache->removals = $this->dnsCache->hostnames = [];
|
||||
|
||||
$this->share = curl_share_init();
|
||||
|
||||
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_SSL_SESSION);
|
||||
|
||||
if (\defined('CURL_LOCK_DATA_CONNECT') && \PHP_VERSION_ID >= 80000) {
|
||||
curl_share_setopt($this->share, \CURLSHOPT_SHARE, \CURL_LOCK_DATA_CONNECT);
|
||||
}
|
||||
}
|
||||
|
||||
private function handlePush($parent, $pushed, array $requestHeaders, int $maxPendingPushes): int
|
||||
{
|
||||
$headers = [];
|
||||
$origin = curl_getinfo($parent, \CURLINFO_EFFECTIVE_URL);
|
||||
|
||||
foreach ($requestHeaders as $h) {
|
||||
if (false !== $i = strpos($h, ':', 1)) {
|
||||
$headers[substr($h, 0, $i)][] = substr($h, 1 + $i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) {
|
||||
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin));
|
||||
|
||||
return \CURL_PUSH_DENY;
|
||||
}
|
||||
|
||||
$url = $headers[':scheme'][0].'://'.$headers[':authority'][0];
|
||||
|
||||
// curl before 7.65 doesn't validate the pushed ":authority" header,
|
||||
// but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
|
||||
// ignoring domains mentioned as alt-name in the certificate for now (same as curl).
|
||||
if (!str_starts_with($origin, $url.'/')) {
|
||||
$this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url));
|
||||
|
||||
return \CURL_PUSH_DENY;
|
||||
}
|
||||
|
||||
if ($maxPendingPushes <= \count($this->pushedResponses)) {
|
||||
$fifoUrl = key($this->pushedResponses);
|
||||
unset($this->pushedResponses[$fifoUrl]);
|
||||
$this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
|
||||
}
|
||||
|
||||
$url .= $headers[':path'][0];
|
||||
$this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url));
|
||||
|
||||
$this->pushedResponses[$url] = new PushedResponse(new CurlResponse($this, $pushed), $headers, $this->openHandles[(int) $parent][1] ?? [], $pushed);
|
||||
|
||||
return \CURL_PUSH_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
/**
|
||||
* Cache for resolved DNS queries.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class DnsCache
|
||||
{
|
||||
/**
|
||||
* Resolved hostnames (hostname => IP address).
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $hostnames = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $removals = [];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $evictions = [];
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
use Http\Client\Exception\NetworkException;
|
||||
use Http\Promise\Promise;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\RequestInterface as Psr7RequestInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\ResponseFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\StreamFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\StreamableInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\StreamWrapper;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class HttplugWaitLoop
|
||||
{
|
||||
private $client;
|
||||
private $promisePool;
|
||||
private $responseFactory;
|
||||
private $streamFactory;
|
||||
|
||||
/**
|
||||
* @param \SplObjectStorage<ResponseInterface, array{Psr7RequestInterface, Promise}>|null $promisePool
|
||||
*/
|
||||
public function __construct(HttpClientInterface $client, ?\SplObjectStorage $promisePool, ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->promisePool = $promisePool;
|
||||
$this->responseFactory = $responseFactory;
|
||||
$this->streamFactory = $streamFactory;
|
||||
}
|
||||
|
||||
public function wait(?ResponseInterface $pendingResponse, ?float $maxDuration = null, ?float $idleTimeout = null): int
|
||||
{
|
||||
if (!$this->promisePool) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$guzzleQueue = \GuzzleHttp\Promise\Utils::queue();
|
||||
|
||||
if (0.0 === $remainingDuration = $maxDuration) {
|
||||
$idleTimeout = 0.0;
|
||||
} elseif (null !== $maxDuration) {
|
||||
$startTime = microtime(true);
|
||||
$idleTimeout = max(0.0, min($maxDuration / 5, $idleTimeout ?? $maxDuration));
|
||||
}
|
||||
|
||||
do {
|
||||
foreach ($this->client->stream($this->promisePool, $idleTimeout) as $response => $chunk) {
|
||||
try {
|
||||
if (null !== $maxDuration && $chunk->isTimeout()) {
|
||||
goto check_duration;
|
||||
}
|
||||
|
||||
if ($chunk->isFirst()) {
|
||||
// Deactivate throwing on 3/4/5xx
|
||||
$response->getStatusCode();
|
||||
}
|
||||
|
||||
if (!$chunk->isLast()) {
|
||||
goto check_duration;
|
||||
}
|
||||
|
||||
if ([, $promise] = $this->promisePool[$response] ?? null) {
|
||||
unset($this->promisePool[$response]);
|
||||
$promise->resolve(self::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $response, true));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
if ([$request, $promise] = $this->promisePool[$response] ?? null) {
|
||||
unset($this->promisePool[$response]);
|
||||
|
||||
if ($e instanceof TransportExceptionInterface) {
|
||||
$e = new NetworkException($e->getMessage(), $request, $e);
|
||||
}
|
||||
|
||||
$promise->reject($e);
|
||||
}
|
||||
}
|
||||
|
||||
$guzzleQueue->run();
|
||||
|
||||
if ($pendingResponse === $response) {
|
||||
return $this->promisePool->count();
|
||||
}
|
||||
|
||||
check_duration:
|
||||
if (null !== $maxDuration && $idleTimeout && $idleTimeout > $remainingDuration = max(0.0, $maxDuration - microtime(true) + $startTime)) {
|
||||
$idleTimeout = $remainingDuration / 5;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$count = $this->promisePool->count()) {
|
||||
return 0;
|
||||
}
|
||||
} while (null === $maxDuration || 0 < $remainingDuration);
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public static function createPsr7Response(ResponseFactoryInterface $responseFactory, StreamFactoryInterface $streamFactory, HttpClientInterface $client, ResponseInterface $response, bool $buffer): Psr7ResponseInterface
|
||||
{
|
||||
$responseParameters = [$response->getStatusCode()];
|
||||
|
||||
foreach ($response->getInfo('response_headers') as $h) {
|
||||
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (?:\d\d\d) (.+)#', $h, $m)) {
|
||||
$responseParameters[1] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
$psrResponse = $responseFactory->createResponse(...$responseParameters);
|
||||
|
||||
foreach ($response->getHeaders(false) as $name => $values) {
|
||||
foreach ($values as $value) {
|
||||
try {
|
||||
$psrResponse = $psrResponse->withAddedHeader($name, $value);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// ignore invalid header
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($response instanceof StreamableInterface) {
|
||||
$body = $streamFactory->createStreamFromResource($response->toStream(false));
|
||||
} elseif (!$buffer) {
|
||||
$body = $streamFactory->createStreamFromResource(StreamWrapper::createResource($response, $client));
|
||||
} else {
|
||||
$body = $streamFactory->createStream($response->getContent(false));
|
||||
}
|
||||
|
||||
if ($body->isSeekable()) {
|
||||
$body->seek(0);
|
||||
}
|
||||
|
||||
return $psrResponse->withBody($body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
/**
|
||||
* Internal representation of the native client's state.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NativeClientState extends ClientState
|
||||
{
|
||||
/** @var int */
|
||||
public $id;
|
||||
/** @var int */
|
||||
public $maxHostConnections = \PHP_INT_MAX;
|
||||
/** @var int */
|
||||
public $responseCount = 0;
|
||||
/** @var string[] */
|
||||
public $dnsCache = [];
|
||||
/** @var bool */
|
||||
public $sleep = false;
|
||||
/** @var int[] */
|
||||
public $hosts = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->id = random_int(\PHP_INT_MIN, \PHP_INT_MAX);
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->responseCount = 0;
|
||||
$this->dnsCache = [];
|
||||
$this->hosts = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\CurlResponse;
|
||||
|
||||
/**
|
||||
* A pushed response with its request headers.
|
||||
*
|
||||
* @author Alexander M. Turek <me@derrabus.de>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class PushedResponse
|
||||
{
|
||||
public $response;
|
||||
|
||||
/** @var string[] */
|
||||
public $requestHeaders;
|
||||
|
||||
public $parentOptions = [];
|
||||
|
||||
public $handle;
|
||||
|
||||
public function __construct(CurlResponse $response, array $requestHeaders, array $parentOptions, $handle)
|
||||
{
|
||||
$this->response = $response;
|
||||
$this->requestHeaders = $requestHeaders;
|
||||
$this->parentOptions = $parentOptions;
|
||||
$this->handle = $handle;
|
||||
}
|
||||
}
|
||||
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/LICENSE
vendored
Normal file
19
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2018-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
124
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/MockHttpClient.php
vendored
Normal file
124
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/MockHttpClient.php
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\MockResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* A test-friendly HttpClient that doesn't make actual HTTP requests.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class MockHttpClient implements HttpClientInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
|
||||
private $responseFactory;
|
||||
private $requestsCount = 0;
|
||||
private $defaultOptions = [];
|
||||
|
||||
/**
|
||||
* @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
|
||||
*/
|
||||
public function __construct($responseFactory = null, ?string $baseUri = 'https://example.com')
|
||||
{
|
||||
$this->setResponseFactory($responseFactory);
|
||||
$this->defaultOptions['base_uri'] = $baseUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|callable[]|ResponseInterface|ResponseInterface[]|iterable|null $responseFactory
|
||||
*/
|
||||
public function setResponseFactory($responseFactory): void
|
||||
{
|
||||
if ($responseFactory instanceof ResponseInterface) {
|
||||
$responseFactory = [$responseFactory];
|
||||
}
|
||||
|
||||
if (!$responseFactory instanceof \Iterator && null !== $responseFactory && !\is_callable($responseFactory)) {
|
||||
$responseFactory = (static function () use ($responseFactory) {
|
||||
yield from $responseFactory;
|
||||
})();
|
||||
}
|
||||
|
||||
$this->responseFactory = $responseFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
[$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true);
|
||||
$url = implode('', $url);
|
||||
|
||||
if (null === $this->responseFactory) {
|
||||
$response = new MockResponse();
|
||||
} elseif (\is_callable($this->responseFactory)) {
|
||||
$response = ($this->responseFactory)($method, $url, $options);
|
||||
} elseif (!$this->responseFactory->valid()) {
|
||||
throw new TransportException('The response factory iterator passed to MockHttpClient is empty.');
|
||||
} else {
|
||||
$responseFactory = $this->responseFactory->current();
|
||||
$response = \is_callable($responseFactory) ? $responseFactory($method, $url, $options) : $responseFactory;
|
||||
$this->responseFactory->next();
|
||||
}
|
||||
++$this->requestsCount;
|
||||
|
||||
if (!$response instanceof ResponseInterface) {
|
||||
throw new TransportException(sprintf('The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "%s" given.', \is_object($response) ? \get_class($response) : \gettype($response)));
|
||||
}
|
||||
|
||||
return MockResponse::fromRequest($method, $url, $options, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
if ($responses instanceof ResponseInterface) {
|
||||
$responses = [$responses];
|
||||
} elseif (!is_iterable($responses)) {
|
||||
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of MockResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
|
||||
}
|
||||
|
||||
return new ResponseStream(MockResponse::stream($responses, $timeout));
|
||||
}
|
||||
|
||||
public function getRequestsCount(): int
|
||||
{
|
||||
return $this->requestsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function withOptions(array $options): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions, true);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->requestsCount = 0;
|
||||
}
|
||||
}
|
||||
483
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/NativeHttpClient.php
vendored
Normal file
483
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/NativeHttpClient.php
vendored
Normal file
@@ -0,0 +1,483 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareTrait;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\NativeResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* A portable implementation of the HttpClientInterface contracts based on PHP stream wrappers.
|
||||
*
|
||||
* PHP stream wrappers are able to fetch response bodies concurrently,
|
||||
* but each request is opened synchronously.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
use LoggerAwareTrait;
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
private static $emptyDefaults = self::OPTIONS_DEFAULTS;
|
||||
|
||||
/** @var NativeClientState */
|
||||
private $multi;
|
||||
|
||||
/**
|
||||
* @param array $defaultOptions Default request's options
|
||||
* @param int $maxHostConnections The maximum number of connections to open
|
||||
*
|
||||
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
|
||||
*/
|
||||
public function __construct(array $defaultOptions = [], int $maxHostConnections = 6)
|
||||
{
|
||||
$this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']);
|
||||
|
||||
if ($defaultOptions) {
|
||||
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
|
||||
}
|
||||
|
||||
$this->multi = new NativeClientState();
|
||||
$this->multi->maxHostConnections = 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
[$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions);
|
||||
|
||||
if ($options['bindto']) {
|
||||
if (file_exists($options['bindto'])) {
|
||||
throw new TransportException(__CLASS__.' cannot bind to local Unix sockets, use e.g. CurlHttpClient instead.');
|
||||
}
|
||||
if (str_starts_with($options['bindto'], 'if!')) {
|
||||
throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.');
|
||||
}
|
||||
if (str_starts_with($options['bindto'], 'host!')) {
|
||||
$options['bindto'] = substr($options['bindto'], 5);
|
||||
}
|
||||
if ((\PHP_VERSION_ID < 80223 || 80300 <= \PHP_VERSION_ID && 80311 < \PHP_VERSION_ID) && '\\' === \DIRECTORY_SEPARATOR && '[' === $options['bindto'][0]) {
|
||||
$options['bindto'] = preg_replace('{^\[[^\]]++\]}', '[$0]', $options['bindto']);
|
||||
}
|
||||
}
|
||||
|
||||
$hasContentLength = isset($options['normalized_headers']['content-length']);
|
||||
$hasBody = '' !== $options['body'] || 'POST' === $method || $hasContentLength;
|
||||
|
||||
$options['body'] = self::getBodyAsString($options['body']);
|
||||
|
||||
if ('chunked' === substr($options['normalized_headers']['transfer-encoding'][0] ?? '', \strlen('Transfer-Encoding: '))) {
|
||||
unset($options['normalized_headers']['transfer-encoding']);
|
||||
$options['headers'] = array_merge(...array_values($options['normalized_headers']));
|
||||
$options['body'] = self::dechunk($options['body']);
|
||||
}
|
||||
if ('' === $options['body'] && $hasBody && !$hasContentLength) {
|
||||
$options['headers'][] = 'Content-Length: 0';
|
||||
}
|
||||
if ($hasBody && !isset($options['normalized_headers']['content-type'])) {
|
||||
$options['headers'][] = 'Content-Type: application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
if (\extension_loaded('zlib') && !isset($options['normalized_headers']['accept-encoding'])) {
|
||||
// gzip is the most widely available algo, no need to deal with deflate
|
||||
$options['headers'][] = 'Accept-Encoding: gzip';
|
||||
}
|
||||
|
||||
if ($options['peer_fingerprint']) {
|
||||
if (isset($options['peer_fingerprint']['pin-sha256']) && 1 === \count($options['peer_fingerprint'])) {
|
||||
throw new TransportException(__CLASS__.' cannot verify "pin-sha256" fingerprints, please provide a "sha256" one.');
|
||||
}
|
||||
|
||||
unset($options['peer_fingerprint']['pin-sha256']);
|
||||
}
|
||||
|
||||
$info = [
|
||||
'response_headers' => [],
|
||||
'url' => $url,
|
||||
'error' => null,
|
||||
'canceled' => false,
|
||||
'http_method' => $method,
|
||||
'http_code' => 0,
|
||||
'redirect_count' => 0,
|
||||
'start_time' => 0.0,
|
||||
'connect_time' => 0.0,
|
||||
'redirect_time' => 0.0,
|
||||
'pretransfer_time' => 0.0,
|
||||
'starttransfer_time' => 0.0,
|
||||
'total_time' => 0.0,
|
||||
'namelookup_time' => 0.0,
|
||||
'size_upload' => 0,
|
||||
'size_download' => 0,
|
||||
'size_body' => \strlen($options['body']),
|
||||
'primary_ip' => '',
|
||||
'primary_port' => 'http:' === $url['scheme'] ? 80 : 443,
|
||||
'debug' => \extension_loaded('curl') ? '' : "* Enable the curl extension for better performance\n",
|
||||
];
|
||||
|
||||
if ($onProgress = $options['on_progress']) {
|
||||
// Memoize the last progress to ease calling the callback periodically when no network transfer happens
|
||||
$lastProgress = [0, 0];
|
||||
$maxDuration = 0 < $options['max_duration'] ? $options['max_duration'] : \INF;
|
||||
$onProgress = static function (...$progress) use ($onProgress, &$lastProgress, &$info, $maxDuration) {
|
||||
if ($info['total_time'] >= $maxDuration) {
|
||||
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
|
||||
}
|
||||
|
||||
$progressInfo = $info;
|
||||
$progressInfo['url'] = implode('', $info['url']);
|
||||
unset($progressInfo['size_body']);
|
||||
|
||||
if ($progress && -1 === $progress[0]) {
|
||||
// Response completed
|
||||
$lastProgress[0] = max($lastProgress);
|
||||
} else {
|
||||
$lastProgress = $progress ?: $lastProgress;
|
||||
}
|
||||
|
||||
$onProgress($lastProgress[0], $lastProgress[1], $progressInfo);
|
||||
};
|
||||
} elseif (0 < $options['max_duration']) {
|
||||
$maxDuration = $options['max_duration'];
|
||||
$onProgress = static function () use (&$info, $maxDuration): void {
|
||||
if ($info['total_time'] >= $maxDuration) {
|
||||
throw new TransportException(sprintf('Max duration was reached for "%s".', implode('', $info['url'])));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Always register a notification callback to compute live stats about the response
|
||||
$notification = static function (int $code, int $severity, ?string $msg, int $msgCode, int $dlNow, int $dlSize) use ($onProgress, &$info) {
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
|
||||
if (\STREAM_NOTIFY_PROGRESS === $code) {
|
||||
$info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time'];
|
||||
$info['size_upload'] += $dlNow ? 0 : $info['size_body'];
|
||||
$info['size_download'] = $dlNow;
|
||||
} elseif (\STREAM_NOTIFY_CONNECT === $code) {
|
||||
$info['connect_time'] = $info['total_time'];
|
||||
$info['debug'] .= $info['request_header'];
|
||||
unset($info['request_header']);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($onProgress) {
|
||||
$onProgress($dlNow, $dlSize);
|
||||
}
|
||||
};
|
||||
|
||||
if ($options['resolve']) {
|
||||
$this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache;
|
||||
}
|
||||
|
||||
$this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, implode('', $url)));
|
||||
|
||||
if (!isset($options['normalized_headers']['user-agent'])) {
|
||||
$options['headers'][] = 'User-Agent: Symfony HttpClient/Native';
|
||||
}
|
||||
|
||||
if (0 < $options['max_duration']) {
|
||||
$options['timeout'] = min($options['max_duration'], $options['timeout']);
|
||||
}
|
||||
|
||||
$bindto = $options['bindto'];
|
||||
if (!$bindto && (70322 === \PHP_VERSION_ID || 70410 === \PHP_VERSION_ID)) {
|
||||
$bindto = '0:0';
|
||||
}
|
||||
|
||||
$context = [
|
||||
'http' => [
|
||||
'protocol_version' => min($options['http_version'] ?: '1.1', '1.1'),
|
||||
'method' => $method,
|
||||
'content' => $options['body'],
|
||||
'ignore_errors' => true,
|
||||
'curl_verify_ssl_peer' => $options['verify_peer'],
|
||||
'curl_verify_ssl_host' => $options['verify_host'],
|
||||
'auto_decode' => false, // Disable dechunk filter, it's incompatible with stream_select()
|
||||
'timeout' => $options['timeout'],
|
||||
'follow_location' => false, // We follow redirects ourselves - the native logic is too limited
|
||||
],
|
||||
'ssl' => array_filter([
|
||||
'verify_peer' => $options['verify_peer'],
|
||||
'verify_peer_name' => $options['verify_host'],
|
||||
'cafile' => $options['cafile'],
|
||||
'capath' => $options['capath'],
|
||||
'local_cert' => $options['local_cert'],
|
||||
'local_pk' => $options['local_pk'],
|
||||
'passphrase' => $options['passphrase'],
|
||||
'ciphers' => $options['ciphers'],
|
||||
'peer_fingerprint' => $options['peer_fingerprint'],
|
||||
'capture_peer_cert_chain' => $options['capture_peer_cert_chain'],
|
||||
'allow_self_signed' => (bool) $options['peer_fingerprint'],
|
||||
'SNI_enabled' => true,
|
||||
'disable_compression' => true,
|
||||
], static function ($v) { return null !== $v; }),
|
||||
'socket' => [
|
||||
'bindto' => $bindto,
|
||||
'tcp_nodelay' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$context = stream_context_create($context, ['notification' => $notification]);
|
||||
|
||||
$resolver = static function ($multi) use ($context, $options, $url, &$info, $onProgress) {
|
||||
[$host, $port] = self::parseHostPort($url, $info);
|
||||
|
||||
if (!isset($options['normalized_headers']['host'])) {
|
||||
$options['headers'][] = 'Host: '.$host.$port;
|
||||
}
|
||||
|
||||
$proxy = self::getProxy($options['proxy'], $url, $options['no_proxy']);
|
||||
|
||||
if (!self::configureHeadersAndProxy($context, $host, $options['headers'], $proxy, 'https:' === $url['scheme'])) {
|
||||
$ip = self::dnsResolve($host, $multi, $info, $onProgress);
|
||||
$url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host));
|
||||
}
|
||||
|
||||
return [self::createRedirectResolver($options, $host, $proxy, $info, $onProgress), implode('', $url)];
|
||||
};
|
||||
|
||||
return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolver, $onProgress, $this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
if ($responses instanceof NativeResponse) {
|
||||
$responses = [$responses];
|
||||
} elseif (!is_iterable($responses)) {
|
||||
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of NativeResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
|
||||
}
|
||||
|
||||
return new ResponseStream(NativeResponse::stream($responses, $timeout));
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->multi->reset();
|
||||
}
|
||||
|
||||
private static function getBodyAsString($body): string
|
||||
{
|
||||
if (\is_resource($body)) {
|
||||
return stream_get_contents($body);
|
||||
}
|
||||
|
||||
if (!$body instanceof \Closure) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$result = '';
|
||||
|
||||
while ('' !== $data = $body(self::$CHUNK_SIZE)) {
|
||||
if (!\is_string($data)) {
|
||||
throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data)));
|
||||
}
|
||||
|
||||
$result .= $data;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the host and the port from the URL.
|
||||
*/
|
||||
private static function parseHostPort(array $url, array &$info): array
|
||||
{
|
||||
if ($port = parse_url($url['authority'], \PHP_URL_PORT) ?: '') {
|
||||
$info['primary_port'] = $port;
|
||||
$port = ':'.$port;
|
||||
} else {
|
||||
$info['primary_port'] = 'http:' === $url['scheme'] ? 80 : 443;
|
||||
}
|
||||
|
||||
return [parse_url($url['authority'], \PHP_URL_HOST), $port];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the IP of the host using the local DNS cache if possible.
|
||||
*/
|
||||
private static function dnsResolve($host, NativeClientState $multi, array &$info, ?\Closure $onProgress): string
|
||||
{
|
||||
$flag = '' !== $host && '[' === $host[0] && ']' === $host[-1] && str_contains($host, ':') ? \FILTER_FLAG_IPV6 : \FILTER_FLAG_IPV4;
|
||||
$ip = \FILTER_FLAG_IPV6 === $flag ? substr($host, 1, -1) : $host;
|
||||
|
||||
if (filter_var($ip, \FILTER_VALIDATE_IP, $flag)) {
|
||||
// The host is already an IP address
|
||||
} elseif (null === $ip = $multi->dnsCache[$host] ?? null) {
|
||||
$info['debug'] .= "* Hostname was NOT found in DNS cache\n";
|
||||
$now = microtime(true);
|
||||
|
||||
if (!$ip = gethostbynamel($host)) {
|
||||
throw new TransportException(sprintf('Could not resolve host "%s".', $host));
|
||||
}
|
||||
|
||||
$multi->dnsCache[$host] = $ip = $ip[0];
|
||||
$info['debug'] .= "* Added {$host}:0:{$ip} to DNS cache\n";
|
||||
$host = $ip;
|
||||
} else {
|
||||
$info['debug'] .= "* Hostname was found in DNS cache\n";
|
||||
$host = str_contains($ip, ':') ? "[$ip]" : $ip;
|
||||
}
|
||||
|
||||
$info['namelookup_time'] = microtime(true) - ($info['start_time'] ?: $now);
|
||||
$info['primary_ip'] = $ip;
|
||||
|
||||
if ($onProgress) {
|
||||
// Notify DNS resolution
|
||||
$onProgress();
|
||||
}
|
||||
|
||||
return $host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles redirects - the native logic is too buggy to be used.
|
||||
*/
|
||||
private static function createRedirectResolver(array $options, string $host, ?array $proxy, array &$info, ?\Closure $onProgress): \Closure
|
||||
{
|
||||
$redirectHeaders = [];
|
||||
if (0 < $maxRedirects = $options['max_redirects']) {
|
||||
$redirectHeaders = ['host' => $host];
|
||||
$redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
|
||||
return 0 !== stripos($h, 'Host:');
|
||||
});
|
||||
|
||||
if (isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) {
|
||||
return 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return static function (NativeClientState $multi, ?string $location, $context) use (&$redirectHeaders, $proxy, &$info, $maxRedirects, $onProgress): ?string {
|
||||
if (null === $location || $info['http_code'] < 300 || 400 <= $info['http_code']) {
|
||||
$info['redirect_url'] = null;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$url = self::parseUrl($location);
|
||||
$locationHasHost = isset($url['authority']);
|
||||
$url = self::resolveUrl($url, $info['url']);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$info['redirect_url'] = null;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$info['redirect_url'] = implode('', $url);
|
||||
|
||||
if ($info['redirect_count'] >= $maxRedirects) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$info['url'] = $url;
|
||||
++$info['redirect_count'];
|
||||
$info['redirect_time'] = microtime(true) - $info['start_time'];
|
||||
|
||||
// Do like curl and browsers: turn POST to GET on 301, 302 and 303
|
||||
if (\in_array($info['http_code'], [301, 302, 303], true)) {
|
||||
$options = stream_context_get_options($context)['http'];
|
||||
|
||||
if ('POST' === $options['method'] || 303 === $info['http_code']) {
|
||||
$info['http_method'] = $options['method'] = 'HEAD' === $options['method'] ? 'HEAD' : 'GET';
|
||||
$options['content'] = '';
|
||||
$filterContentHeaders = static function ($h) {
|
||||
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
|
||||
};
|
||||
$options['header'] = array_filter($options['header'], $filterContentHeaders);
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
|
||||
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
|
||||
|
||||
if (\PHP_VERSION_ID >= 80300) {
|
||||
stream_context_set_options($context, ['http' => $options]);
|
||||
} else {
|
||||
stream_context_set_option($context, ['http' => $options]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[$host, $port] = self::parseHostPort($url, $info);
|
||||
|
||||
if ($locationHasHost) {
|
||||
// Authorization and Cookie headers MUST NOT follow except for the initial host name
|
||||
$requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
$requestHeaders[] = 'Host: '.$host.$port;
|
||||
$dnsResolve = !self::configureHeadersAndProxy($context, $host, $requestHeaders, $proxy, 'https:' === $url['scheme']);
|
||||
} else {
|
||||
$dnsResolve = isset(stream_context_get_options($context)['ssl']['peer_name']);
|
||||
}
|
||||
|
||||
if ($dnsResolve) {
|
||||
$ip = self::dnsResolve($host, $multi, $info, $onProgress);
|
||||
$url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host));
|
||||
}
|
||||
|
||||
return implode('', $url);
|
||||
};
|
||||
}
|
||||
|
||||
private static function configureHeadersAndProxy($context, string $host, array $requestHeaders, ?array $proxy, bool $isSsl): bool
|
||||
{
|
||||
if (null === $proxy) {
|
||||
stream_context_set_option($context, 'http', 'header', $requestHeaders);
|
||||
stream_context_set_option($context, 'ssl', 'peer_name', $host);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Matching "no_proxy" should follow the behavior of curl
|
||||
|
||||
foreach ($proxy['no_proxy'] as $rule) {
|
||||
$dotRule = '.'.ltrim($rule, '.');
|
||||
|
||||
if ('*' === $rule || $host === $rule || str_ends_with($host, $dotRule)) {
|
||||
stream_context_set_option($context, 'http', 'proxy', null);
|
||||
stream_context_set_option($context, 'http', 'request_fulluri', false);
|
||||
stream_context_set_option($context, 'http', 'header', $requestHeaders);
|
||||
stream_context_set_option($context, 'ssl', 'peer_name', $host);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $proxy['auth']) {
|
||||
$requestHeaders[] = 'Proxy-Authorization: '.$proxy['auth'];
|
||||
}
|
||||
|
||||
stream_context_set_option($context, 'http', 'proxy', $proxy['url']);
|
||||
stream_context_set_option($context, 'http', 'request_fulluri', !$isSsl);
|
||||
stream_context_set_option($context, 'http', 'header', $requestHeaders);
|
||||
stream_context_set_option($context, 'ssl', 'peer_name', null);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncContext;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation\IpUtils;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Decorator that blocks requests to private networks by default.
|
||||
*
|
||||
* @author Hallison Boaventura <hallisonboaventura@gmail.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwareInterface, ResetInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
use AsyncDecoratorTrait;
|
||||
|
||||
private const PRIVATE_SUBNETS = [
|
||||
'127.0.0.0/8',
|
||||
'10.0.0.0/8',
|
||||
'192.168.0.0/16',
|
||||
'172.16.0.0/12',
|
||||
'169.254.0.0/16',
|
||||
'0.0.0.0/8',
|
||||
'240.0.0.0/4',
|
||||
'::1/128',
|
||||
'fc00::/7',
|
||||
'fe80::/10',
|
||||
'::ffff:0:0/96',
|
||||
'::/128',
|
||||
];
|
||||
|
||||
private $defaultOptions = self::OPTIONS_DEFAULTS;
|
||||
private $client;
|
||||
private $subnets;
|
||||
private $ipFlags;
|
||||
private $dnsCache;
|
||||
|
||||
/**
|
||||
* @param string|array|null $subnets String or array of subnets using CIDR notation that should be considered private.
|
||||
* If null is passed, the standard private subnets will be used.
|
||||
*/
|
||||
public function __construct(HttpClientInterface $client, $subnets = null)
|
||||
{
|
||||
if (!(\is_array($subnets) || \is_string($subnets) || null === $subnets)) {
|
||||
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be of the type array, string or null. "%s" given.', __METHOD__, get_debug_type($subnets)));
|
||||
}
|
||||
|
||||
if (!class_exists(IpUtils::class)) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" if the HttpFoundation component is not installed. Try running "composer require symfony/http-foundation".', __CLASS__));
|
||||
}
|
||||
|
||||
if (null === $subnets) {
|
||||
$ipFlags = \FILTER_FLAG_IPV4 | \FILTER_FLAG_IPV6;
|
||||
} else {
|
||||
$ipFlags = 0;
|
||||
foreach ((array) $subnets as $subnet) {
|
||||
$ipFlags |= str_contains($subnet, ':') ? \FILTER_FLAG_IPV6 : \FILTER_FLAG_IPV4;
|
||||
}
|
||||
}
|
||||
|
||||
if (!\defined('STREAM_PF_INET6')) {
|
||||
$ipFlags &= ~\FILTER_FLAG_IPV6;
|
||||
}
|
||||
|
||||
$this->client = $client;
|
||||
$this->subnets = null !== $subnets ? (array) $subnets : null;
|
||||
$this->ipFlags = $ipFlags;
|
||||
$this->dnsCache = new \ArrayObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
[$url, $options] = self::prepareRequest($method, $url, $options, $this->defaultOptions, true);
|
||||
|
||||
$redirectHeaders = parse_url($url['authority']);
|
||||
$host = $redirectHeaders['host'];
|
||||
$url = implode('', $url);
|
||||
$dnsCache = $this->dnsCache;
|
||||
|
||||
$ip = self::dnsResolve($dnsCache, $host, $this->ipFlags, $options);
|
||||
self::ipCheck($ip, $this->subnets, $this->ipFlags, $host, $url);
|
||||
|
||||
$onProgress = $options['on_progress'] ?? null;
|
||||
$subnets = $this->subnets;
|
||||
$ipFlags = $this->ipFlags;
|
||||
$lastPrimaryIp = '';
|
||||
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use ($onProgress, $subnets, $ipFlags, &$lastPrimaryIp): void {
|
||||
if (!\in_array($info['primary_ip'] ?? '', ['', $lastPrimaryIp], true)) {
|
||||
self::ipCheck($info['primary_ip'], $subnets, $ipFlags, null, $info['url']);
|
||||
$lastPrimaryIp = $info['primary_ip'];
|
||||
}
|
||||
|
||||
null !== $onProgress && $onProgress($dlNow, $dlSize, $info);
|
||||
};
|
||||
|
||||
if (0 >= $maxRedirects = $options['max_redirects']) {
|
||||
return new AsyncResponse($this->client, $method, $url, $options);
|
||||
}
|
||||
|
||||
$options['max_redirects'] = 0;
|
||||
$redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = $options['headers'];
|
||||
|
||||
if (isset($options['normalized_headers']['host']) || isset($options['normalized_headers']['authorization']) || isset($options['normalized_headers']['cookie'])) {
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], static function ($h) {
|
||||
return 0 !== stripos($h, 'Host:') && 0 !== stripos($h, 'Authorization:') && 0 !== stripos($h, 'Cookie:');
|
||||
});
|
||||
}
|
||||
|
||||
return new AsyncResponse($this->client, $method, $url, $options, static function (ChunkInterface $chunk, AsyncContext $context) use (&$method, &$options, $maxRedirects, &$redirectHeaders, $subnets, $ipFlags, $dnsCache): \Generator {
|
||||
if (null !== $chunk->getError() || $chunk->isTimeout() || !$chunk->isFirst()) {
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$statusCode = $context->getStatusCode();
|
||||
|
||||
if ($statusCode < 300 || 400 <= $statusCode || null === $url = $context->getInfo('redirect_url')) {
|
||||
$context->passthru();
|
||||
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$host = parse_url($url, \PHP_URL_HOST);
|
||||
$ip = self::dnsResolve($dnsCache, $host, $ipFlags, $options);
|
||||
self::ipCheck($ip, $subnets, $ipFlags, $host, $url);
|
||||
|
||||
// Do like curl and browsers: turn POST to GET on 301, 302 and 303
|
||||
if (303 === $statusCode || 'POST' === $method && \in_array($statusCode, [301, 302], true)) {
|
||||
$method = 'HEAD' === $method ? 'HEAD' : 'GET';
|
||||
unset($options['body'], $options['json']);
|
||||
|
||||
if (isset($options['normalized_headers']['content-length']) || isset($options['normalized_headers']['content-type']) || isset($options['normalized_headers']['transfer-encoding'])) {
|
||||
$filterContentHeaders = static function ($h) {
|
||||
return 0 !== stripos($h, 'Content-Length:') && 0 !== stripos($h, 'Content-Type:') && 0 !== stripos($h, 'Transfer-Encoding:');
|
||||
};
|
||||
$options['header'] = array_filter($options['header'], $filterContentHeaders);
|
||||
$redirectHeaders['no_auth'] = array_filter($redirectHeaders['no_auth'], $filterContentHeaders);
|
||||
$redirectHeaders['with_auth'] = array_filter($redirectHeaders['with_auth'], $filterContentHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
// Authorization and Cookie headers MUST NOT follow except for the initial host name
|
||||
$options['headers'] = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
|
||||
|
||||
static $redirectCount = 0;
|
||||
$context->setInfo('redirect_count', ++$redirectCount);
|
||||
|
||||
$context->replaceRequest($method, $url, $options);
|
||||
|
||||
if ($redirectCount >= $maxRedirects) {
|
||||
$context->passthru();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
if ($this->client instanceof LoggerAwareInterface) {
|
||||
$this->client->setLogger($logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function withOptions(array $options): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->client = $this->client->withOptions($options);
|
||||
$clone->defaultOptions = self::mergeDefaultOptions($options, $this->defaultOptions);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
$this->dnsCache->exchangeArray([]);
|
||||
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
|
||||
private static function dnsResolve(\ArrayObject $dnsCache, string $host, int $ipFlags, array &$options): string
|
||||
{
|
||||
if ($ip = filter_var(trim($host, '[]'), \FILTER_VALIDATE_IP) ?: $options['resolve'][$host] ?? false) {
|
||||
return $ip;
|
||||
}
|
||||
|
||||
if ($dnsCache->offsetExists($host)) {
|
||||
return $dnsCache[$host];
|
||||
}
|
||||
|
||||
if ((\FILTER_FLAG_IPV4 & $ipFlags) && $ip = gethostbynamel($host)) {
|
||||
return $options['resolve'][$host] = $dnsCache[$host] = $ip[0];
|
||||
}
|
||||
|
||||
if (!(\FILTER_FLAG_IPV6 & $ipFlags)) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
if ($ip = dns_get_record($host, \DNS_AAAA)) {
|
||||
$ip = $ip[0]['ipv6'];
|
||||
} elseif (extension_loaded('sockets')) {
|
||||
if (!$info = socket_addrinfo_lookup($host, 0, ['ai_socktype' => \SOCK_STREAM, 'ai_family' => \AF_INET6])) {
|
||||
return $host;
|
||||
}
|
||||
|
||||
$ip = socket_addrinfo_explain($info[0])['ai_addr']['sin6_addr'];
|
||||
} elseif ('localhost' === $host || 'localhost.' === $host) {
|
||||
$ip = '::1';
|
||||
} else {
|
||||
return $host;
|
||||
}
|
||||
|
||||
return $options['resolve'][$host] = $dnsCache[$host] = $ip;
|
||||
}
|
||||
|
||||
private static function ipCheck(string $ip, ?array $subnets, int $ipFlags, ?string $host, string $url): void
|
||||
{
|
||||
if (null === $subnets) {
|
||||
// Quick check, but not reliable enough, see https://github.com/php/php-src/issues/16944
|
||||
$ipFlags |= \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
|
||||
}
|
||||
|
||||
if (false !== filter_var($ip, \FILTER_VALIDATE_IP, $ipFlags) && !IpUtils::checkIp($ip, $subnets ?? self::PRIVATE_SUBNETS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (null !== $host) {
|
||||
$type = 'Host';
|
||||
} else {
|
||||
$host = $ip;
|
||||
$type = 'IP';
|
||||
}
|
||||
|
||||
throw new TransportException($type.\sprintf(' "%s" is blocked for "%s".', $host, $url));
|
||||
}
|
||||
}
|
||||
231
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Psr18Client.php
vendored
Normal file
231
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/Psr18Client.php
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use Http\Discovery\Exception\NotFoundException;
|
||||
use Http\Discovery\Psr17FactoryDiscovery;
|
||||
use KadenceWP\KadenceBlocks\Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use KadenceWP\KadenceBlocks\Nyholm\Psr7\Request;
|
||||
use KadenceWP\KadenceBlocks\Nyholm\Psr7\Uri;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Client\ClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Client\NetworkExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Client\RequestExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\RequestFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\RequestInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\ResponseFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\StreamFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\StreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\UriFactoryInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\UriInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\HttplugWaitLoop;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\StreamableInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\StreamWrapper;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface as HttpClientResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
if (!interface_exists(RequestFactoryInterface::class)) {
|
||||
throw new \LogicException('You cannot use the "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Psr18Client" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".');
|
||||
}
|
||||
|
||||
if (!interface_exists(ClientInterface::class)) {
|
||||
throw new \LogicException('You cannot use the "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Psr18Client" as the "psr/http-client" package is not installed. Try running "composer require psr/http-client".');
|
||||
}
|
||||
|
||||
/**
|
||||
* An adapter to turn a Symfony HttpClientInterface into a PSR-18 ClientInterface.
|
||||
*
|
||||
* Run "composer require psr/http-client" to install the base ClientInterface. Run
|
||||
* "composer require nyholm/psr7" to install an efficient implementation of response
|
||||
* and stream factories with flex-provided autowiring aliases.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface
|
||||
{
|
||||
private $client;
|
||||
private $responseFactory;
|
||||
private $streamFactory;
|
||||
|
||||
public function __construct(?HttpClientInterface $client = null, ?ResponseFactoryInterface $responseFactory = null, ?StreamFactoryInterface $streamFactory = null)
|
||||
{
|
||||
$this->client = $client ?? HttpClient::create();
|
||||
$this->responseFactory = $responseFactory;
|
||||
$this->streamFactory = $streamFactory ?? ($responseFactory instanceof StreamFactoryInterface ? $responseFactory : null);
|
||||
|
||||
if (null !== $this->responseFactory && null !== $this->streamFactory) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!class_exists(Psr17Factory::class) && !class_exists(Psr17FactoryDiscovery::class)) {
|
||||
throw new \LogicException('You cannot use the "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Psr18Client" as no PSR-17 factories have been provided. Try running "composer require nyholm/psr7".');
|
||||
}
|
||||
|
||||
try {
|
||||
$psr17Factory = class_exists(Psr17Factory::class, false) ? new Psr17Factory() : null;
|
||||
$this->responseFactory = $this->responseFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findResponseFactory();
|
||||
$this->streamFactory = $this->streamFactory ?? $psr17Factory ?? Psr17FactoryDiscovery::findStreamFactory();
|
||||
} catch (NotFoundException $e) {
|
||||
throw new \LogicException('You cannot use the "KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\HttplugClient" as no PSR-17 factories have been found. Try running "composer require nyholm/psr7".', 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendRequest(RequestInterface $request): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$body = $request->getBody();
|
||||
|
||||
if ($body->isSeekable()) {
|
||||
$body->seek(0);
|
||||
}
|
||||
|
||||
$options = [
|
||||
'headers' => $request->getHeaders(),
|
||||
'body' => $body->getContents(),
|
||||
];
|
||||
|
||||
if ('1.0' === $request->getProtocolVersion()) {
|
||||
$options['http_version'] = '1.0';
|
||||
}
|
||||
|
||||
$response = $this->client->request($request->getMethod(), (string) $request->getUri(), $options);
|
||||
|
||||
return HttplugWaitLoop::createPsr7Response($this->responseFactory, $this->streamFactory, $this->client, $response, false);
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
if ($e instanceof \InvalidArgumentException) {
|
||||
throw new Psr18RequestException($e, $request);
|
||||
}
|
||||
|
||||
throw new Psr18NetworkException($e, $request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createRequest(string $method, $uri): RequestInterface
|
||||
{
|
||||
if ($this->responseFactory instanceof RequestFactoryInterface) {
|
||||
return $this->responseFactory->createRequest($method, $uri);
|
||||
}
|
||||
|
||||
if (class_exists(Request::class)) {
|
||||
return new Request($method, $uri);
|
||||
}
|
||||
|
||||
if (class_exists(Psr17FactoryDiscovery::class)) {
|
||||
return Psr17FactoryDiscovery::findRequestFactory()->createRequest($method, $uri);
|
||||
}
|
||||
|
||||
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createStream(string $content = ''): StreamInterface
|
||||
{
|
||||
$stream = $this->streamFactory->createStream($content);
|
||||
|
||||
if ($stream->isSeekable()) {
|
||||
$stream->seek(0);
|
||||
}
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
|
||||
{
|
||||
return $this->streamFactory->createStreamFromFile($filename, $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createStreamFromResource($resource): StreamInterface
|
||||
{
|
||||
return $this->streamFactory->createStreamFromResource($resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createUri(string $uri = ''): UriInterface
|
||||
{
|
||||
if ($this->responseFactory instanceof UriFactoryInterface) {
|
||||
return $this->responseFactory->createUri($uri);
|
||||
}
|
||||
|
||||
if (class_exists(Uri::class)) {
|
||||
return new Uri($uri);
|
||||
}
|
||||
|
||||
if (class_exists(Psr17FactoryDiscovery::class)) {
|
||||
return Psr17FactoryDiscovery::findUrlFactory()->createUri($uri);
|
||||
}
|
||||
|
||||
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface
|
||||
{
|
||||
private $request;
|
||||
|
||||
public function __construct(TransportExceptionInterface $e, RequestInterface $request)
|
||||
{
|
||||
parent::__construct($e->getMessage(), 0, $e);
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface
|
||||
{
|
||||
private $request;
|
||||
|
||||
public function __construct(TransportExceptionInterface $e, RequestInterface $request)
|
||||
{
|
||||
parent::__construct($e->getMessage(), 0, $e);
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
public function getRequest(): RequestInterface
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
}
|
||||
27
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/README.md
vendored
Normal file
27
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/README.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
HttpClient component
|
||||
====================
|
||||
|
||||
The HttpClient component provides powerful methods to fetch HTTP resources synchronously or asynchronously.
|
||||
|
||||
Sponsor
|
||||
-------
|
||||
|
||||
The Httpclient component for Symfony 5.4/6.0 is [backed][1] by [Klaxoon][2].
|
||||
|
||||
Klaxoon is a platform that empowers organizations to run effective and
|
||||
productive workshops easily in a hybrid environment. Anytime, Anywhere.
|
||||
|
||||
Help Symfony by [sponsoring][3] its development!
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/http_client.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
|
||||
[1]: https://symfony.com/backers
|
||||
[2]: https://klaxoon.com
|
||||
[3]: https://symfony.com/sponsor
|
||||
@@ -0,0 +1,460 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use Amp\ByteStream\StreamException;
|
||||
use Amp\CancellationTokenSource;
|
||||
use Amp\Coroutine;
|
||||
use Amp\Deferred;
|
||||
use Amp\Http\Client\HttpException;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Client\Response;
|
||||
use Amp\Loop;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\InformationalChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\HttpClientTrait;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\AmpBody;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\AmpClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\Canary;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AmpResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
use TransportResponseTrait;
|
||||
|
||||
private static $nextId = 'a';
|
||||
|
||||
private $multi;
|
||||
private $options;
|
||||
private $onProgress;
|
||||
|
||||
private static $delay;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(AmpClientState $multi, Request $request, array $options, ?LoggerInterface $logger)
|
||||
{
|
||||
$this->multi = $multi;
|
||||
$this->options = &$options;
|
||||
$this->logger = $logger;
|
||||
$this->timeout = $options['timeout'];
|
||||
$this->shouldBuffer = $options['buffer'];
|
||||
|
||||
if ($this->inflate = \extension_loaded('zlib') && !$request->hasHeader('accept-encoding')) {
|
||||
$request->setHeader('Accept-Encoding', 'gzip');
|
||||
}
|
||||
|
||||
$this->initializer = static function (self $response) {
|
||||
return null !== $response->options;
|
||||
};
|
||||
|
||||
$info = &$this->info;
|
||||
$headers = &$this->headers;
|
||||
$canceller = new CancellationTokenSource();
|
||||
$handle = &$this->handle;
|
||||
|
||||
$info['url'] = (string) $request->getUri();
|
||||
$info['http_method'] = $request->getMethod();
|
||||
$info['start_time'] = null;
|
||||
$info['redirect_url'] = null;
|
||||
$info['redirect_time'] = 0.0;
|
||||
$info['redirect_count'] = 0;
|
||||
$info['size_upload'] = 0.0;
|
||||
$info['size_download'] = 0.0;
|
||||
$info['upload_content_length'] = -1.0;
|
||||
$info['download_content_length'] = -1.0;
|
||||
$info['user_data'] = $options['user_data'];
|
||||
$info['max_duration'] = $options['max_duration'];
|
||||
$info['debug'] = '';
|
||||
|
||||
$onProgress = $options['on_progress'] ?? static function () {};
|
||||
$onProgress = $this->onProgress = static function () use (&$info, $onProgress) {
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
$onProgress((int) $info['size_download'], ((int) (1 + $info['download_content_length']) ?: 1) - 1, (array) $info);
|
||||
};
|
||||
|
||||
$pauseDeferred = new Deferred();
|
||||
$pause = new Success();
|
||||
|
||||
$throttleWatcher = null;
|
||||
|
||||
$this->id = $id = self::$nextId++;
|
||||
Loop::defer(static function () use ($request, $multi, &$id, &$info, &$headers, $canceller, &$options, $onProgress, &$handle, $logger, &$pause) {
|
||||
return new Coroutine(self::generateResponse($request, $multi, $id, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause));
|
||||
});
|
||||
|
||||
$info['pause_handler'] = static function (float $duration) use (&$throttleWatcher, &$pauseDeferred, &$pause) {
|
||||
if (null !== $throttleWatcher) {
|
||||
Loop::cancel($throttleWatcher);
|
||||
}
|
||||
|
||||
$pause = $pauseDeferred->promise();
|
||||
|
||||
if ($duration <= 0) {
|
||||
$deferred = $pauseDeferred;
|
||||
$pauseDeferred = new Deferred();
|
||||
$deferred->resolve();
|
||||
} else {
|
||||
$throttleWatcher = Loop::delay(ceil(1000 * $duration), static function () use (&$pauseDeferred) {
|
||||
$deferred = $pauseDeferred;
|
||||
$pauseDeferred = new Deferred();
|
||||
$deferred->resolve();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$multi->lastTimeout = null;
|
||||
$multi->openHandles[$id] = $id;
|
||||
++$multi->responseCount;
|
||||
|
||||
$this->canary = new Canary(static function () use ($canceller, $multi, $id) {
|
||||
$canceller->cancel();
|
||||
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
return null !== $type ? $this->info[$type] ?? null : $this->info;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
// Clear the DNS cache when all requests completed
|
||||
if (0 >= --$this->multi->responseCount) {
|
||||
$this->multi->responseCount = 0;
|
||||
$this->multi->dnsCache = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (isset($runningResponses[0])) {
|
||||
$runningResponses[0][1][$response->id] = $response;
|
||||
} else {
|
||||
$runningResponses[0] = [$response->multi, [$response->id => $response]];
|
||||
}
|
||||
|
||||
if (!isset($response->multi->openHandles[$response->id])) {
|
||||
$response->multi->handlesActivity[$response->id][] = null;
|
||||
$response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param AmpClientState $multi
|
||||
*/
|
||||
private static function perform(ClientState $multi, ?array &$responses = null): void
|
||||
{
|
||||
if ($responses) {
|
||||
foreach ($responses as $response) {
|
||||
try {
|
||||
if ($response->info['start_time']) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
($response->onProgress)();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[$response->id][] = null;
|
||||
$multi->handlesActivity[$response->id][] = $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param AmpClientState $multi
|
||||
*/
|
||||
private static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
$timeout += microtime(true);
|
||||
self::$delay = Loop::defer(static function () use ($timeout) {
|
||||
if (0 < $timeout -= microtime(true)) {
|
||||
self::$delay = Loop::delay(ceil(1000 * $timeout), [Loop::class, 'stop']);
|
||||
} else {
|
||||
Loop::stop();
|
||||
}
|
||||
});
|
||||
|
||||
Loop::run();
|
||||
|
||||
return null === self::$delay ? 1 : 0;
|
||||
}
|
||||
|
||||
private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause)
|
||||
{
|
||||
$request->setInformationalResponseHandler(static function (Response $response) use ($multi, $id, &$info, &$headers) {
|
||||
self::addResponseHeaders($response, $info, $headers);
|
||||
$multi->handlesActivity[$id][] = new InformationalChunk($response->getStatus(), $response->getHeaders());
|
||||
self::stopLoop();
|
||||
});
|
||||
|
||||
try {
|
||||
/* @var Response $response */
|
||||
if (null === $response = yield from self::getPushedResponse($request, $multi, $info, $headers, $options, $logger)) {
|
||||
$logger && $logger->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url']));
|
||||
|
||||
$response = yield from self::followRedirects($request, $multi, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause);
|
||||
}
|
||||
|
||||
$options = null;
|
||||
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
|
||||
if ('HEAD' === $response->getRequest()->getMethod() || \in_array($info['http_code'], [204, 304], true)) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
self::stopLoop();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($response->hasHeader('content-length')) {
|
||||
$info['download_content_length'] = (float) $response->getHeader('content-length');
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
|
||||
while (true) {
|
||||
self::stopLoop();
|
||||
|
||||
yield $pause;
|
||||
|
||||
if (null === $data = yield $body->read()) {
|
||||
break;
|
||||
}
|
||||
|
||||
$info['size_download'] += \strlen($data);
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
}
|
||||
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = $e;
|
||||
} finally {
|
||||
$info['download_content_length'] = $info['size_download'];
|
||||
}
|
||||
|
||||
self::stopLoop();
|
||||
}
|
||||
|
||||
private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause)
|
||||
{
|
||||
yield $pause;
|
||||
|
||||
$originRequest->setBody(new AmpBody($options['body'], $info, $onProgress));
|
||||
$response = yield $multi->request($options, $originRequest, $canceller->getToken(), $info, $onProgress, $handle);
|
||||
$previousUrl = null;
|
||||
|
||||
while (true) {
|
||||
self::addResponseHeaders($response, $info, $headers);
|
||||
$status = $response->getStatus();
|
||||
|
||||
if (!\in_array($status, [301, 302, 303, 307, 308], true) || null === $location = $response->getHeader('location')) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$urlResolver = new class() {
|
||||
use HttpClientTrait {
|
||||
parseUrl as public;
|
||||
resolveUrl as public;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
$previousUrl = $previousUrl ?? $urlResolver::parseUrl($info['url']);
|
||||
$location = $urlResolver::parseUrl($location);
|
||||
$location = $urlResolver::resolveUrl($location, $previousUrl);
|
||||
$info['redirect_url'] = implode('', $location);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (0 >= $options['max_redirects'] || $info['redirect_count'] >= $options['max_redirects']) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$logger && $logger->info(sprintf('Redirecting: "%s %s"', $status, $info['url']));
|
||||
|
||||
try {
|
||||
// Discard body of redirects
|
||||
while (null !== yield $response->getBody()->read()) {
|
||||
}
|
||||
} catch (HttpException|StreamException $e) {
|
||||
// Ignore streaming errors on previous responses
|
||||
}
|
||||
|
||||
++$info['redirect_count'];
|
||||
$info['url'] = $info['redirect_url'];
|
||||
$info['redirect_url'] = null;
|
||||
$previousUrl = $location;
|
||||
|
||||
$request = new Request($info['url'], $info['http_method']);
|
||||
$request->setProtocolVersions($originRequest->getProtocolVersions());
|
||||
$request->setTcpConnectTimeout($originRequest->getTcpConnectTimeout());
|
||||
$request->setTlsHandshakeTimeout($originRequest->getTlsHandshakeTimeout());
|
||||
$request->setTransferTimeout($originRequest->getTransferTimeout());
|
||||
|
||||
if (\in_array($status, [301, 302, 303], true)) {
|
||||
$originRequest->removeHeader('transfer-encoding');
|
||||
$originRequest->removeHeader('content-length');
|
||||
$originRequest->removeHeader('content-type');
|
||||
|
||||
// Do like curl and browsers: turn POST to GET on 301, 302 and 303
|
||||
if ('POST' === $response->getRequest()->getMethod() || 303 === $status) {
|
||||
$info['http_method'] = 'HEAD' === $response->getRequest()->getMethod() ? 'HEAD' : 'GET';
|
||||
$request->setMethod($info['http_method']);
|
||||
}
|
||||
} else {
|
||||
$request->setBody(AmpBody::rewind($response->getRequest()->getBody()));
|
||||
}
|
||||
|
||||
foreach ($originRequest->getRawHeaders() as [$name, $value]) {
|
||||
$request->addHeader($name, $value);
|
||||
}
|
||||
|
||||
if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) {
|
||||
$request->removeHeader('authorization');
|
||||
$request->removeHeader('cookie');
|
||||
$request->removeHeader('host');
|
||||
}
|
||||
|
||||
yield $pause;
|
||||
|
||||
$response = yield $multi->request($options, $request, $canceller->getToken(), $info, $onProgress, $handle);
|
||||
$info['redirect_time'] = microtime(true) - $info['start_time'];
|
||||
}
|
||||
}
|
||||
|
||||
private static function addResponseHeaders(Response $response, array &$info, array &$headers): void
|
||||
{
|
||||
$info['http_code'] = $response->getStatus();
|
||||
|
||||
if ($headers) {
|
||||
$info['debug'] .= "< \r\n";
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
$h = sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatus(), $response->getReason());
|
||||
$info['debug'] .= "< {$h}\r\n";
|
||||
$info['response_headers'][] = $h;
|
||||
|
||||
foreach ($response->getRawHeaders() as [$name, $value]) {
|
||||
$headers[strtolower($name)][] = $value;
|
||||
$h = $name.': '.$value;
|
||||
$info['debug'] .= "< {$h}\r\n";
|
||||
$info['response_headers'][] = $h;
|
||||
}
|
||||
|
||||
$info['debug'] .= "< \r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts pushed responses only if their headers related to authentication match the request.
|
||||
*/
|
||||
private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger)
|
||||
{
|
||||
if ('' !== $options['body']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$authority = $request->getUri()->getAuthority();
|
||||
|
||||
foreach ($multi->pushedResponses[$authority] ?? [] as $i => [$pushedUrl, $pushDeferred, $pushedRequest, $pushedResponse, $parentOptions]) {
|
||||
if ($info['url'] !== $pushedUrl || $info['http_method'] !== $pushedRequest->getMethod()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($parentOptions as $k => $v) {
|
||||
if ($options[$k] !== $v) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['authorization', 'cookie', 'range', 'proxy-authorization'] as $k) {
|
||||
if ($pushedRequest->getHeaderArray($k) !== $request->getHeaderArray($k)) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$response = yield $pushedResponse;
|
||||
|
||||
foreach ($response->getHeaderArray('vary') as $vary) {
|
||||
foreach (preg_split('/\s*+,\s*+/', $vary) as $v) {
|
||||
if ('*' === $v || ($pushedRequest->getHeaderArray($v) !== $request->getHeaderArray($v) && 'accept-encoding' !== strtolower($v))) {
|
||||
$logger && $logger->debug(sprintf('Skipping pushed response: "%s"', $info['url']));
|
||||
continue 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pushDeferred->resolve();
|
||||
$logger && $logger->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url']));
|
||||
self::addResponseHeaders($response, $info, $headers);
|
||||
unset($multi->pushedResponses[$authority][$i]);
|
||||
|
||||
if (!$multi->pushedResponses[$authority]) {
|
||||
unset($multi->pushedResponses[$authority]);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
private static function stopLoop(): void
|
||||
{
|
||||
if (null !== self::$delay) {
|
||||
Loop::cancel(self::$delay);
|
||||
self::$delay = null;
|
||||
}
|
||||
|
||||
Loop::defer([Loop::class, 'stop']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\DataChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\LastChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A DTO to work with AsyncResponse.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class AsyncContext
|
||||
{
|
||||
private $passthru;
|
||||
private $client;
|
||||
private $response;
|
||||
private $info = [];
|
||||
private $content;
|
||||
private $offset;
|
||||
|
||||
public function __construct(&$passthru, HttpClientInterface $client, ResponseInterface &$response, array &$info, $content, int $offset)
|
||||
{
|
||||
$this->passthru = &$passthru;
|
||||
$this->client = $client;
|
||||
$this->response = &$response;
|
||||
$this->info = &$info;
|
||||
$this->content = $content;
|
||||
$this->offset = $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP status without consuming the response.
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->response->getInfo('http_code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers without consuming the response.
|
||||
*/
|
||||
public function getHeaders(): array
|
||||
{
|
||||
$headers = [];
|
||||
|
||||
foreach ($this->response->getInfo('response_headers') as $h) {
|
||||
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? ([123456789]\d\d)(?: |$)#', $h, $m)) {
|
||||
$headers = [];
|
||||
} elseif (2 === \count($m = explode(':', $h, 2))) {
|
||||
$headers[strtolower($m[0])][] = ltrim($m[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|null The PHP stream resource where the content is buffered, if it is
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new chunk of content.
|
||||
*/
|
||||
public function createChunk(string $data): ChunkInterface
|
||||
{
|
||||
return new DataChunk($this->offset, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the request for the given number of seconds.
|
||||
*/
|
||||
public function pause(float $duration): void
|
||||
{
|
||||
if (\is_callable($pause = $this->response->getInfo('pause_handler'))) {
|
||||
$pause($duration);
|
||||
} elseif (0 < $duration) {
|
||||
usleep((int) (1E6 * $duration));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the request and returns the last chunk to yield.
|
||||
*/
|
||||
public function cancel(): ChunkInterface
|
||||
{
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
$this->response->cancel();
|
||||
|
||||
return new LastChunk();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current info of the response.
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if (null !== $type) {
|
||||
return $this->info[$type] ?? $this->response->getInfo($type);
|
||||
}
|
||||
|
||||
return $this->info + $this->response->getInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches an info to the response.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setInfo(string $type, $value): self
|
||||
{
|
||||
if ('canceled' === $type && $value !== $this->info['canceled']) {
|
||||
throw new \LogicException('You cannot set the "canceled" info directly.');
|
||||
}
|
||||
|
||||
if (null === $value) {
|
||||
unset($this->info[$type]);
|
||||
} else {
|
||||
$this->info[$type] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently processed response.
|
||||
*/
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the currently processed response by doing a new request.
|
||||
*/
|
||||
public function replaceRequest(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$this->info['previous_info'][] = $info = $this->response->getInfo();
|
||||
if (null !== $onProgress = $options['on_progress'] ?? null) {
|
||||
$thisInfo = &$this->info;
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info);
|
||||
};
|
||||
}
|
||||
if (0 < ($info['max_duration'] ?? 0) && 0 < ($info['total_time'] ?? 0)) {
|
||||
if (0 >= $options['max_duration'] = $info['max_duration'] - $info['total_time']) {
|
||||
throw new TransportException(sprintf('Max duration was reached for "%s".', $info['url']));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->response = $this->client->request($method, $url, ['buffer' => false] + $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the currently processed response by another one.
|
||||
*/
|
||||
public function replaceResponse(ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$this->info['previous_info'][] = $this->response->getInfo();
|
||||
|
||||
return $this->response = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces or removes the chunk filter iterator.
|
||||
*
|
||||
* @param ?callable(ChunkInterface, self): ?\Iterator $passthru
|
||||
*/
|
||||
public function passthru(?callable $passthru = null): void
|
||||
{
|
||||
$this->passthru = $passthru ?? static function ($chunk, $context) {
|
||||
$context->passthru = null;
|
||||
|
||||
yield $chunk;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\LastChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Provides a single extension point to process a response's content stream.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class AsyncResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
|
||||
private const FIRST_CHUNK_YIELDED = 1;
|
||||
private const LAST_CHUNK_YIELDED = 2;
|
||||
|
||||
private $client;
|
||||
private $response;
|
||||
private $info = ['canceled' => false];
|
||||
private $passthru;
|
||||
private $stream;
|
||||
private $yieldedState;
|
||||
|
||||
/**
|
||||
* @param ?callable(ChunkInterface, AsyncContext): ?\Iterator $passthru
|
||||
*/
|
||||
public function __construct(HttpClientInterface $client, string $method, string $url, array $options, ?callable $passthru = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->shouldBuffer = $options['buffer'] ?? true;
|
||||
|
||||
if (null !== $onProgress = $options['on_progress'] ?? null) {
|
||||
$thisInfo = &$this->info;
|
||||
$options['on_progress'] = static function (int $dlNow, int $dlSize, array $info) use (&$thisInfo, $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $thisInfo + $info);
|
||||
};
|
||||
}
|
||||
$this->response = $client->request($method, $url, ['buffer' => false] + $options);
|
||||
$this->passthru = $passthru;
|
||||
$this->initializer = static function (self $response, ?float $timeout = null) {
|
||||
if (null === $response->shouldBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
foreach (self::stream([$response], $timeout) as $chunk) {
|
||||
if ($chunk->isTimeout() && $response->passthru) {
|
||||
// Timeouts thrown during initialization are transport errors
|
||||
foreach (self::passthru($response->client, $response, new ErrorChunk($response->offset, new TransportException($chunk->getError()))) as $chunk) {
|
||||
if ($chunk->isFirst()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if ($chunk->isFirst()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (\array_key_exists('user_data', $options)) {
|
||||
$this->info['user_data'] = $options['user_data'];
|
||||
}
|
||||
if (\array_key_exists('max_duration', $options)) {
|
||||
$this->info['max_duration'] = $options['max_duration'];
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
return $this->response->getStatusCode();
|
||||
}
|
||||
|
||||
public function getHeaders(bool $throw = true): array
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
$headers = $this->response->getHeaders(false);
|
||||
|
||||
if ($throw) {
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if ('debug' === ($type ?? 'debug')) {
|
||||
$debug = implode('', array_column($this->info['previous_info'] ?? [], 'debug'));
|
||||
$debug .= $this->response->getInfo('debug');
|
||||
|
||||
if ('debug' === $type) {
|
||||
return $debug;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $type) {
|
||||
return $this->info[$type] ?? $this->response->getInfo($type);
|
||||
}
|
||||
|
||||
return array_merge($this->info + $this->response->getInfo(), ['debug' => $debug]);
|
||||
}
|
||||
|
||||
public function toStream(bool $throw = true)
|
||||
{
|
||||
if ($throw) {
|
||||
// Ensure headers arrived
|
||||
$this->getHeaders(true);
|
||||
}
|
||||
|
||||
$handle = function () {
|
||||
$stream = $this->response instanceof StreamableInterface ? $this->response->toStream(false) : StreamWrapper::createResource($this->response);
|
||||
|
||||
return stream_get_meta_data($stream)['wrapper_data']->stream_cast(\STREAM_CAST_FOR_SELECT);
|
||||
};
|
||||
|
||||
$stream = StreamWrapper::createResource($this);
|
||||
stream_get_meta_data($stream)['wrapper_data']
|
||||
->bindHandles($handle, $this->content);
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
if ($this->info['canceled']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
$this->close();
|
||||
$client = $this->client;
|
||||
$this->client = null;
|
||||
|
||||
if (!$this->passthru) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
foreach (self::passthru($client, $this, new LastChunk()) as $chunk) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
$this->passthru = null;
|
||||
} catch (ExceptionInterface $e) {
|
||||
// ignore any errors when canceling
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$httpException = null;
|
||||
|
||||
if ($this->initializer && null === $this->getInfo('error')) {
|
||||
try {
|
||||
self::initialize($this, -0.0);
|
||||
$this->getHeaders(true);
|
||||
} catch (HttpExceptionInterface $httpException) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->passthru && null === $this->getInfo('error')) {
|
||||
$this->info['canceled'] = true;
|
||||
|
||||
try {
|
||||
foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) {
|
||||
// no-op
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
// ignore any errors when destructing
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $httpException) {
|
||||
throw $httpException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function stream(iterable $responses, ?float $timeout = null, ?string $class = null): \Generator
|
||||
{
|
||||
while ($responses) {
|
||||
$wrappedResponses = [];
|
||||
$asyncMap = new \SplObjectStorage();
|
||||
$client = null;
|
||||
|
||||
foreach ($responses as $r) {
|
||||
if (!$r instanceof self) {
|
||||
throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of AsyncResponse objects, "%s" given.', $class ?? static::class, get_debug_type($r)));
|
||||
}
|
||||
|
||||
if (null !== $e = $r->info['error'] ?? null) {
|
||||
yield $r => $chunk = new ErrorChunk($r->offset, new TransportException($e));
|
||||
$chunk->didThrow() ?: $chunk->getContent();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null === $client) {
|
||||
$client = $r->client;
|
||||
} elseif ($r->client !== $client) {
|
||||
throw new TransportException('Cannot stream AsyncResponse objects with many clients.');
|
||||
}
|
||||
|
||||
$asyncMap[$r->response] = $r;
|
||||
$wrappedResponses[] = $r->response;
|
||||
|
||||
if ($r->stream) {
|
||||
yield from self::passthruStream($response = $r->response, $r, new FirstChunk(), $asyncMap);
|
||||
|
||||
if (!isset($asyncMap[$response])) {
|
||||
array_pop($wrappedResponses);
|
||||
}
|
||||
|
||||
if ($r->response !== $response && !isset($asyncMap[$r->response])) {
|
||||
$asyncMap[$r->response] = $r;
|
||||
$wrappedResponses[] = $r->response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$client || !$wrappedResponses) {
|
||||
return;
|
||||
}
|
||||
|
||||
$chunk = null;
|
||||
foreach ($client->stream($wrappedResponses, $timeout) as $response => $chunk) {
|
||||
$r = $asyncMap[$response];
|
||||
|
||||
if (null === $chunk->getError()) {
|
||||
if ($chunk->isFirst()) {
|
||||
// Ensure no exception is thrown on destruct for the wrapped response
|
||||
$r->response->getStatusCode();
|
||||
} elseif (0 === $r->offset && null === $r->content && $chunk->isLast()) {
|
||||
$r->content = fopen('php://memory', 'w+');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$r->passthru) {
|
||||
if (null !== $chunk->getError() || $chunk->isLast()) {
|
||||
unset($asyncMap[$response]);
|
||||
} elseif (null !== $r->content && '' !== ($content = $chunk->getContent()) && \strlen($content) !== fwrite($r->content, $content)) {
|
||||
$chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
|
||||
$r->info['error'] = $chunk->getError();
|
||||
$r->response->cancel();
|
||||
}
|
||||
|
||||
yield $r => $chunk;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (null !== $chunk->getError()) {
|
||||
// no-op
|
||||
} elseif ($chunk->isFirst()) {
|
||||
$r->yieldedState = self::FIRST_CHUNK_YIELDED;
|
||||
} elseif (self::FIRST_CHUNK_YIELDED !== $r->yieldedState && null === $chunk->getInformationalStatus()) {
|
||||
throw new \LogicException(sprintf('Instance of "%s" is already consumed and cannot be managed by "%s". A decorated client should not call any of the response\'s methods in its "request()" method.', get_debug_type($response), $class ?? static::class));
|
||||
}
|
||||
|
||||
foreach (self::passthru($r->client, $r, $chunk, $asyncMap) as $chunk) {
|
||||
yield $r => $chunk;
|
||||
}
|
||||
|
||||
if ($r->response !== $response && isset($asyncMap[$response])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $chunk) {
|
||||
throw new \LogicException(\sprintf('"%s" is not compliant with HttpClientInterface: its "stream()" method didn\'t yield any chunks when it should have.', get_debug_type($client)));
|
||||
}
|
||||
if (null === $chunk->getError() && $chunk->isLast()) {
|
||||
$r->yieldedState = self::LAST_CHUNK_YIELDED;
|
||||
}
|
||||
if (null === $chunk->getError() && self::LAST_CHUNK_YIELDED !== $r->yieldedState && $r->response === $response && null !== $r->client) {
|
||||
throw new \LogicException('A chunk passthru must yield an "isLast()" chunk before ending a stream.');
|
||||
}
|
||||
|
||||
$responses = [];
|
||||
foreach ($asyncMap as $response) {
|
||||
$r = $asyncMap[$response];
|
||||
|
||||
if (null !== $r->client) {
|
||||
$responses[] = $asyncMap[$response];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
|
||||
*/
|
||||
private static function passthru(HttpClientInterface $client, self $r, ChunkInterface $chunk, ?\SplObjectStorage $asyncMap = null): \Generator
|
||||
{
|
||||
$r->stream = null;
|
||||
$response = $r->response;
|
||||
$context = new AsyncContext($r->passthru, $client, $r->response, $r->info, $r->content, $r->offset);
|
||||
if (null === $stream = ($r->passthru)($chunk, $context)) {
|
||||
if ($r->response === $response && (null !== $chunk->getError() || $chunk->isLast())) {
|
||||
throw new \LogicException('A chunk passthru cannot swallow the last chunk.');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$stream instanceof \Iterator) {
|
||||
throw new \LogicException(sprintf('A chunk passthru must return an "Iterator", "%s" returned.', get_debug_type($stream)));
|
||||
}
|
||||
$r->stream = $stream;
|
||||
|
||||
yield from self::passthruStream($response, $r, null, $asyncMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SplObjectStorage<ResponseInterface, AsyncResponse>|null $asyncMap
|
||||
*/
|
||||
private static function passthruStream(ResponseInterface $response, self $r, ?ChunkInterface $chunk, ?\SplObjectStorage $asyncMap): \Generator
|
||||
{
|
||||
while (true) {
|
||||
try {
|
||||
if (null !== $chunk && $r->stream) {
|
||||
$r->stream->next();
|
||||
}
|
||||
|
||||
if (!$r->stream || !$r->stream->valid() || !$r->stream) {
|
||||
$r->stream = null;
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
unset($asyncMap[$response]);
|
||||
$r->stream = null;
|
||||
$r->info['error'] = $e->getMessage();
|
||||
$r->response->cancel();
|
||||
|
||||
yield $r => $chunk = new ErrorChunk($r->offset, $e);
|
||||
$chunk->didThrow() ?: $chunk->getContent();
|
||||
break;
|
||||
}
|
||||
|
||||
$chunk = $r->stream->current();
|
||||
|
||||
if (!$chunk instanceof ChunkInterface) {
|
||||
throw new \LogicException(sprintf('A chunk passthru must yield instances of "%s", "%s" yielded.', ChunkInterface::class, get_debug_type($chunk)));
|
||||
}
|
||||
|
||||
if (null !== $chunk->getError()) {
|
||||
// no-op
|
||||
} elseif ($chunk->isFirst()) {
|
||||
$e = $r->openBuffer();
|
||||
|
||||
yield $r => $chunk;
|
||||
|
||||
if ($r->initializer && null === $r->getInfo('error')) {
|
||||
// Ensure the HTTP status code is always checked
|
||||
$r->getHeaders(true);
|
||||
}
|
||||
|
||||
if (null === $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$r->response->cancel();
|
||||
$chunk = new ErrorChunk($r->offset, $e);
|
||||
} elseif ('' !== $content = $chunk->getContent()) {
|
||||
if (null !== $r->shouldBuffer) {
|
||||
throw new \LogicException('A chunk passthru must yield an "isFirst()" chunk before any content chunk.');
|
||||
}
|
||||
|
||||
if (null !== $r->content && \strlen($content) !== fwrite($r->content, $content)) {
|
||||
$chunk = new ErrorChunk($r->offset, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($content))));
|
||||
$r->info['error'] = $chunk->getError();
|
||||
$r->response->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $chunk->getError() || $chunk->isLast()) {
|
||||
$stream = $r->stream;
|
||||
$r->stream = null;
|
||||
unset($asyncMap[$response]);
|
||||
}
|
||||
|
||||
if (null === $chunk->getError()) {
|
||||
$r->offset += \strlen($content);
|
||||
|
||||
yield $r => $chunk;
|
||||
|
||||
if (!$chunk->isLast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stream->next();
|
||||
|
||||
if ($stream->valid()) {
|
||||
throw new \LogicException('A chunk passthru cannot yield after an "isLast()" chunk.');
|
||||
}
|
||||
|
||||
$r->passthru = null;
|
||||
} else {
|
||||
if ($chunk instanceof ErrorChunk) {
|
||||
$chunk->didThrow(false);
|
||||
} else {
|
||||
try {
|
||||
$chunk = new ErrorChunk($chunk->getOffset(), !$chunk->isTimeout() ?: $chunk->getError());
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$chunk = new ErrorChunk($chunk->getOffset(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
yield $r => $chunk;
|
||||
$chunk->didThrow() ?: $chunk->getContent();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function openBuffer(): ?\Throwable
|
||||
{
|
||||
if (null === $shouldBuffer = $this->shouldBuffer) {
|
||||
throw new \LogicException('A chunk passthru cannot yield more than one "isFirst()" chunk.');
|
||||
}
|
||||
|
||||
$e = $this->shouldBuffer = null;
|
||||
|
||||
if ($shouldBuffer instanceof \Closure) {
|
||||
try {
|
||||
$shouldBuffer = $shouldBuffer($this->getHeaders(false));
|
||||
|
||||
if (null !== $e = $this->response->getInfo('error')) {
|
||||
throw new TransportException($e);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->info['error'] = $e->getMessage();
|
||||
$this->response->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $shouldBuffer) {
|
||||
$this->content = fopen('php://temp', 'w+');
|
||||
} elseif (\is_resource($shouldBuffer)) {
|
||||
$this->content = $shouldBuffer;
|
||||
}
|
||||
|
||||
return $e;
|
||||
}
|
||||
|
||||
private function close(): void
|
||||
{
|
||||
$this->response->cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\JsonException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\RedirectionException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\ServerException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
|
||||
/**
|
||||
* Implements common logic for response classes.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait CommonResponseTrait
|
||||
{
|
||||
/**
|
||||
* @var callable|null A callback that tells whether we're waiting for response headers
|
||||
*/
|
||||
private $initializer;
|
||||
private $shouldBuffer;
|
||||
private $content;
|
||||
private $offset = 0;
|
||||
private $jsonData;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent(bool $throw = true): string
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
|
||||
if (null === $this->content) {
|
||||
$content = null;
|
||||
|
||||
foreach (self::stream([$this]) as $chunk) {
|
||||
if (!$chunk->isLast()) {
|
||||
$content .= $chunk->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $content) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
if (null === $this->content) {
|
||||
throw new TransportException('Cannot get the content of the response twice: buffering is disabled.');
|
||||
}
|
||||
} else {
|
||||
foreach (self::stream([$this]) as $chunk) {
|
||||
// Chunks are buffered in $this->content already
|
||||
}
|
||||
}
|
||||
|
||||
rewind($this->content);
|
||||
|
||||
return stream_get_contents($this->content);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toArray(bool $throw = true): array
|
||||
{
|
||||
if ('' === $content = $this->getContent($throw)) {
|
||||
throw new JsonException('Response body is empty.');
|
||||
}
|
||||
|
||||
if (null !== $this->jsonData) {
|
||||
return $this->jsonData;
|
||||
}
|
||||
|
||||
try {
|
||||
$content = json_decode($content, true, 512, \JSON_BIGINT_AS_STRING | (\PHP_VERSION_ID >= 70300 ? \JSON_THROW_ON_ERROR : 0));
|
||||
} catch (\JsonException $e) {
|
||||
throw new JsonException($e->getMessage().sprintf(' for "%s".', $this->getInfo('url')), $e->getCode());
|
||||
}
|
||||
|
||||
if (\PHP_VERSION_ID < 70300 && \JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new JsonException(json_last_error_msg().sprintf(' for "%s".', $this->getInfo('url')), json_last_error());
|
||||
}
|
||||
|
||||
if (!\is_array($content)) {
|
||||
throw new JsonException(sprintf('JSON content was expected to decode to an array, "%s" returned for "%s".', get_debug_type($content), $this->getInfo('url')));
|
||||
}
|
||||
|
||||
if (null !== $this->content) {
|
||||
// Option "buffer" is true
|
||||
return $this->jsonData = $content;
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toStream(bool $throw = true)
|
||||
{
|
||||
if ($throw) {
|
||||
// Ensure headers arrived
|
||||
$this->getHeaders($throw);
|
||||
}
|
||||
|
||||
$stream = StreamWrapper::createResource($this);
|
||||
stream_get_meta_data($stream)['wrapper_data']
|
||||
->bindHandles($this->handle, $this->content);
|
||||
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the response and all its network handles.
|
||||
*/
|
||||
abstract protected function close(): void;
|
||||
|
||||
private static function initialize(self $response): void
|
||||
{
|
||||
if (null !== $response->getInfo('error')) {
|
||||
throw new TransportException($response->getInfo('error'));
|
||||
}
|
||||
|
||||
try {
|
||||
if (($response->initializer)($response, -0.0)) {
|
||||
foreach (self::stream([$response], -0.0) as $chunk) {
|
||||
if ($chunk->isFirst()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// Persist timeouts thrown during initialization
|
||||
$response->info['error'] = $e->getMessage();
|
||||
$response->close();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$response->initializer = null;
|
||||
}
|
||||
|
||||
private function checkStatusCode()
|
||||
{
|
||||
$code = $this->getInfo('http_code');
|
||||
|
||||
if (500 <= $code) {
|
||||
throw new ServerException($this);
|
||||
}
|
||||
|
||||
if (400 <= $code) {
|
||||
throw new ClientException($this);
|
||||
}
|
||||
|
||||
if (300 <= $code) {
|
||||
throw new RedirectionException($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\InformationalChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\Canary;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\CurlClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CurlResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait {
|
||||
getContent as private doGetContent;
|
||||
}
|
||||
use TransportResponseTrait;
|
||||
|
||||
private $multi;
|
||||
private $debugBuffer;
|
||||
|
||||
/**
|
||||
* @param \CurlHandle|resource|string $ch
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(CurlClientState $multi, $ch, ?array $options = null, ?LoggerInterface $logger = null, string $method = 'GET', ?callable $resolveRedirect = null, ?int $curlVersion = null)
|
||||
{
|
||||
$this->multi = $multi;
|
||||
|
||||
if (\is_resource($ch) || $ch instanceof \CurlHandle) {
|
||||
$this->handle = $ch;
|
||||
$this->debugBuffer = fopen('php://temp', 'w+');
|
||||
if (0x074000 === $curlVersion) {
|
||||
fwrite($this->debugBuffer, 'Due to a bug in curl 7.64.0, the debug log is disabled; use another version to work around the issue.');
|
||||
} else {
|
||||
curl_setopt($ch, \CURLOPT_VERBOSE, true);
|
||||
curl_setopt($ch, \CURLOPT_STDERR, $this->debugBuffer);
|
||||
}
|
||||
} else {
|
||||
$this->info['url'] = $ch;
|
||||
$ch = $this->handle;
|
||||
}
|
||||
|
||||
$this->id = $id = (int) $ch;
|
||||
$this->logger = $logger;
|
||||
$this->shouldBuffer = $options['buffer'] ?? true;
|
||||
$this->timeout = $options['timeout'] ?? null;
|
||||
$this->info['http_method'] = $method;
|
||||
$this->info['user_data'] = $options['user_data'] ?? null;
|
||||
$this->info['max_duration'] = $options['max_duration'] ?? null;
|
||||
$this->info['start_time'] = $this->info['start_time'] ?? microtime(true);
|
||||
$info = &$this->info;
|
||||
$headers = &$this->headers;
|
||||
$debugBuffer = $this->debugBuffer;
|
||||
|
||||
if (!$info['response_headers']) {
|
||||
// Used to keep track of what we're waiting for
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, \in_array($method, ['GET', 'HEAD', 'OPTIONS', 'TRACE'], true) && 1.0 < (float) ($options['http_version'] ?? 1.1) ? 'H2' : 'H0'); // H = headers + retry counter
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int {
|
||||
return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger);
|
||||
});
|
||||
|
||||
if (null === $options) {
|
||||
// Pushed response: buffer until requested
|
||||
curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
curl_pause($ch, \CURLPAUSE_RECV);
|
||||
|
||||
return \strlen($data);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$execCounter = $multi->execCounter;
|
||||
$this->info['pause_handler'] = static function (float $duration) use ($ch, $multi, $execCounter) {
|
||||
if (0 < $duration) {
|
||||
if ($execCounter === $multi->execCounter) {
|
||||
curl_multi_remove_handle($multi->handle, $ch);
|
||||
}
|
||||
|
||||
$lastExpiry = end($multi->pauseExpiries);
|
||||
$multi->pauseExpiries[(int) $ch] = $duration += microtime(true);
|
||||
if (false !== $lastExpiry && $lastExpiry > $duration) {
|
||||
asort($multi->pauseExpiries);
|
||||
}
|
||||
curl_pause($ch, \CURLPAUSE_ALL);
|
||||
} else {
|
||||
unset($multi->pauseExpiries[(int) $ch]);
|
||||
curl_pause($ch, \CURLPAUSE_CONT);
|
||||
curl_multi_add_handle($multi->handle, $ch);
|
||||
}
|
||||
};
|
||||
|
||||
$this->inflate = !isset($options['normalized_headers']['accept-encoding']);
|
||||
curl_pause($ch, \CURLPAUSE_CONT);
|
||||
|
||||
if ($onProgress = $options['on_progress']) {
|
||||
$url = isset($info['url']) ? ['url' => $info['url']] : [];
|
||||
curl_setopt($ch, \CURLOPT_NOPROGRESS, false);
|
||||
curl_setopt($ch, \CURLOPT_PROGRESSFUNCTION, static function ($ch, $dlSize, $dlNow) use ($onProgress, &$info, $url, $multi, $debugBuffer) {
|
||||
try {
|
||||
rewind($debugBuffer);
|
||||
$debug = ['debug' => stream_get_contents($debugBuffer)];
|
||||
$onProgress($dlNow, $dlSize, $url + curl_getinfo($ch) + $info + $debug);
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[(int) $ch][] = null;
|
||||
$multi->handlesActivity[(int) $ch][] = $e;
|
||||
|
||||
return 1; // Abort the request
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
|
||||
if ('H' === (curl_getinfo($ch, \CURLINFO_PRIVATE)[0] ?? null)) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = new TransportException(sprintf('Unsupported protocol for "%s"', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_WRITEFUNCTION, static function ($ch, string $data) use ($multi, $id): int {
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
|
||||
return \strlen($data);
|
||||
});
|
||||
|
||||
$multi->handlesActivity[$id][] = $data;
|
||||
|
||||
return \strlen($data);
|
||||
});
|
||||
|
||||
$this->initializer = static function (self $response) {
|
||||
$waitFor = curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE);
|
||||
|
||||
return 'H' === $waitFor[0];
|
||||
};
|
||||
|
||||
// Schedule the request in a non-blocking way
|
||||
$multi->lastTimeout = null;
|
||||
$multi->openHandles[$id] = [$ch, $options];
|
||||
curl_multi_add_handle($multi->handle, $ch);
|
||||
|
||||
$this->canary = new Canary(static function () use ($ch, $multi, $id) {
|
||||
unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, '_0');
|
||||
|
||||
if ($multi->performing) {
|
||||
return;
|
||||
}
|
||||
|
||||
curl_multi_remove_handle($multi->handle, $ch);
|
||||
curl_setopt_array($ch, [
|
||||
\CURLOPT_NOPROGRESS => true,
|
||||
\CURLOPT_PROGRESSFUNCTION => null,
|
||||
\CURLOPT_HEADERFUNCTION => null,
|
||||
\CURLOPT_WRITEFUNCTION => null,
|
||||
\CURLOPT_READFUNCTION => null,
|
||||
\CURLOPT_INFILE => null,
|
||||
]);
|
||||
|
||||
if (!$multi->openHandles) {
|
||||
// Schedule DNS cache eviction for the next request
|
||||
$multi->dnsCache->evictions = $multi->dnsCache->evictions ?: $multi->dnsCache->removals;
|
||||
$multi->dnsCache->removals = $multi->dnsCache->hostnames = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if (!$info = $this->finalInfo) {
|
||||
$info = array_merge($this->info, curl_getinfo($this->handle));
|
||||
$info['url'] = $this->info['url'] ?? $info['url'];
|
||||
$info['redirect_url'] = $this->info['redirect_url'] ?? null;
|
||||
|
||||
// workaround curl not subtracting the time offset for pushed responses
|
||||
if (isset($this->info['url']) && $info['start_time'] / 1000 < $info['total_time']) {
|
||||
$info['total_time'] -= $info['starttransfer_time'] ?: $info['total_time'];
|
||||
$info['starttransfer_time'] = 0.0;
|
||||
}
|
||||
|
||||
rewind($this->debugBuffer);
|
||||
$info['debug'] = stream_get_contents($this->debugBuffer);
|
||||
$waitFor = curl_getinfo($this->handle, \CURLINFO_PRIVATE);
|
||||
|
||||
if ('H' !== $waitFor[0] && 'C' !== $waitFor[0]) {
|
||||
curl_setopt($this->handle, \CURLOPT_VERBOSE, false);
|
||||
rewind($this->debugBuffer);
|
||||
ftruncate($this->debugBuffer, 0);
|
||||
$this->finalInfo = $info;
|
||||
}
|
||||
}
|
||||
|
||||
return null !== $type ? $info[$type] ?? null : $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent(bool $throw = true): string
|
||||
{
|
||||
$performing = $this->multi->performing;
|
||||
$this->multi->performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE);
|
||||
|
||||
try {
|
||||
return $this->doGetContent($throw);
|
||||
} finally {
|
||||
$this->multi->performing = $performing;
|
||||
}
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
if (null === $this->timeout) {
|
||||
return; // Unused pushed response
|
||||
}
|
||||
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
if (\is_resource($this->handle) || $this->handle instanceof \CurlHandle) {
|
||||
curl_setopt($this->handle, \CURLOPT_VERBOSE, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (isset($runningResponses[$i = (int) $response->multi->handle])) {
|
||||
$runningResponses[$i][1][$response->id] = $response;
|
||||
} else {
|
||||
$runningResponses[$i] = [$response->multi, [$response->id => $response]];
|
||||
}
|
||||
|
||||
if ('_0' === curl_getinfo($ch = $response->handle, \CURLINFO_PRIVATE)) {
|
||||
// Response already completed
|
||||
$response->multi->handlesActivity[$response->id][] = null;
|
||||
$response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param CurlClientState $multi
|
||||
*/
|
||||
private static function perform(ClientState $multi, ?array &$responses = null): void
|
||||
{
|
||||
if ($multi->performing) {
|
||||
if ($responses) {
|
||||
$response = current($responses);
|
||||
$multi->handlesActivity[(int) $response->handle][] = null;
|
||||
$multi->handlesActivity[(int) $response->handle][] = new TransportException(sprintf('Userland callback cannot use the client nor the response while processing "%s".', curl_getinfo($response->handle, \CURLINFO_EFFECTIVE_URL)));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$multi->performing = true;
|
||||
++$multi->execCounter;
|
||||
$active = 0;
|
||||
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) {
|
||||
}
|
||||
|
||||
if (\CURLM_OK !== $err) {
|
||||
throw new TransportException(curl_multi_strerror($err));
|
||||
}
|
||||
|
||||
while ($info = curl_multi_info_read($multi->handle)) {
|
||||
if (\CURLMSG_DONE !== $info['msg']) {
|
||||
continue;
|
||||
}
|
||||
$result = $info['result'];
|
||||
$id = (int) $ch = $info['handle'];
|
||||
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
|
||||
|
||||
if (\in_array($result, [\CURLE_SEND_ERROR, \CURLE_RECV_ERROR, /* CURLE_HTTP2 */ 16, /* CURLE_HTTP2_STREAM */ 92], true) && $waitFor[1] && 'C' !== $waitFor[0]) {
|
||||
curl_multi_remove_handle($multi->handle, $ch);
|
||||
$waitFor[1] = (string) ((int) $waitFor[1] - 1); // decrement the retry counter
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
|
||||
curl_setopt($ch, \CURLOPT_FORBID_REUSE, true);
|
||||
|
||||
if (0 === curl_multi_add_handle($multi->handle, $ch)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (\CURLE_RECV_ERROR === $result && 'H' === $waitFor[0] && 400 <= ($responses[(int) $ch]->info['http_code'] ?? 0)) {
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
}
|
||||
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) || (curl_error($ch) === 'OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 0' && -1.0 === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) && \in_array('close', array_map('strtolower', $responses[$id]->headers['connection']), true)) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
|
||||
}
|
||||
} finally {
|
||||
$multi->performing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param CurlClientState $multi
|
||||
*/
|
||||
private static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
if (\PHP_VERSION_ID < 70211) {
|
||||
// workaround https://bugs.php.net/76480
|
||||
$timeout = min($timeout, 0.01);
|
||||
}
|
||||
|
||||
if ($multi->pauseExpiries) {
|
||||
$now = microtime(true);
|
||||
|
||||
foreach ($multi->pauseExpiries as $id => $pauseExpiry) {
|
||||
if ($now < $pauseExpiry) {
|
||||
$timeout = min($timeout, $pauseExpiry - $now);
|
||||
break;
|
||||
}
|
||||
|
||||
unset($multi->pauseExpiries[$id]);
|
||||
curl_pause($multi->openHandles[$id][0], \CURLPAUSE_CONT);
|
||||
curl_multi_add_handle($multi->handle, $multi->openHandles[$id][0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (0 !== $selected = curl_multi_select($multi->handle, $timeout)) {
|
||||
return $selected;
|
||||
}
|
||||
|
||||
if ($multi->pauseExpiries && 0 < $timeout -= microtime(true) - $now) {
|
||||
usleep((int) (1E6 * $timeout));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses header lines as curl yields them to us.
|
||||
*/
|
||||
private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int
|
||||
{
|
||||
if (!str_ends_with($data, "\r\n")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
|
||||
|
||||
if ('H' !== $waitFor[0]) {
|
||||
return \strlen($data); // Ignore HTTP trailers
|
||||
}
|
||||
|
||||
$statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE);
|
||||
|
||||
if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) {
|
||||
return \strlen($data); // Ignore headers from responses to CONNECT requests
|
||||
}
|
||||
|
||||
if ("\r\n" !== $data) {
|
||||
// Regular header line: add it to the list
|
||||
self::addResponseHeaders([substr($data, 0, -2)], $info, $headers);
|
||||
|
||||
if (!str_starts_with($data, 'HTTP/')) {
|
||||
if (0 === stripos($data, 'Location:')) {
|
||||
$location = trim(substr($data, 9, -2));
|
||||
}
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
|
||||
if (\function_exists('openssl_x509_read') && $certinfo = curl_getinfo($ch, \CURLINFO_CERTINFO)) {
|
||||
$info['peer_certificate_chain'] = array_map('openssl_x509_read', array_column($certinfo, 'Cert'));
|
||||
}
|
||||
|
||||
if (300 <= $info['http_code'] && $info['http_code'] < 400) {
|
||||
if (curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
|
||||
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
|
||||
} elseif (303 === $info['http_code'] || ('POST' === $info['http_method'] && \in_array($info['http_code'], [301, 302], true))) {
|
||||
curl_setopt($ch, \CURLOPT_POSTFIELDS, '');
|
||||
}
|
||||
}
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
|
||||
// End of headers: handle informational responses, redirects, etc.
|
||||
|
||||
if (200 > $statusCode) {
|
||||
$multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers);
|
||||
$location = null;
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
|
||||
$info['redirect_url'] = null;
|
||||
|
||||
if (300 <= $statusCode && $statusCode < 400 && null !== $location) {
|
||||
if ($noContent = 303 === $statusCode || ('POST' === $info['http_method'] && \in_array($statusCode, [301, 302], true))) {
|
||||
$info['http_method'] = 'HEAD' === $info['http_method'] ? 'HEAD' : 'GET';
|
||||
curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, $info['http_method']);
|
||||
}
|
||||
|
||||
if (null === $info['redirect_url'] = $resolveRedirect($ch, $location, $noContent)) {
|
||||
$options['max_redirects'] = curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT);
|
||||
curl_setopt($ch, \CURLOPT_FOLLOWLOCATION, false);
|
||||
curl_setopt($ch, \CURLOPT_MAXREDIRS, $options['max_redirects']);
|
||||
}
|
||||
}
|
||||
|
||||
if (401 === $statusCode && isset($options['auth_ntlm']) && 0 === strncasecmp($headers['www-authenticate'][0] ?? '', 'NTLM ', 5)) {
|
||||
// Continue with NTLM auth
|
||||
} elseif ($statusCode < 300 || 400 <= $statusCode || null === $location || curl_getinfo($ch, \CURLINFO_REDIRECT_COUNT) === $options['max_redirects']) {
|
||||
// Headers and redirects completed, time to get the response's content
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
|
||||
if ('HEAD' === $info['http_method'] || \in_array($statusCode, [204, 304], true)) {
|
||||
$waitFor = '_0'; // no content expected
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
} else {
|
||||
$waitFor[0] = 'C'; // C = content
|
||||
}
|
||||
|
||||
curl_setopt($ch, \CURLOPT_PRIVATE, $waitFor);
|
||||
} elseif (null !== $info['redirect_url'] && $logger) {
|
||||
$logger->info(sprintf('Redirecting: "%s %s"', $info['http_code'], $info['redirect_url']));
|
||||
}
|
||||
|
||||
$location = null;
|
||||
|
||||
return \strlen($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use GuzzleHttp\Promise\Create;
|
||||
use GuzzleHttp\Promise\PromiseInterface as GuzzlePromiseInterface;
|
||||
use Http\Promise\Promise as HttplugPromiseInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class HttplugPromise implements HttplugPromiseInterface
|
||||
{
|
||||
private $promise;
|
||||
|
||||
public function __construct(GuzzlePromiseInterface $promise)
|
||||
{
|
||||
$this->promise = $promise;
|
||||
}
|
||||
|
||||
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): self
|
||||
{
|
||||
return new self($this->promise->then(
|
||||
$this->wrapThenCallback($onFulfilled),
|
||||
$this->wrapThenCallback($onRejected)
|
||||
));
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->promise->cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getState(): string
|
||||
{
|
||||
return $this->promise->getState();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return Psr7ResponseInterface|mixed
|
||||
*/
|
||||
public function wait($unwrap = true)
|
||||
{
|
||||
$result = $this->promise->wait($unwrap);
|
||||
|
||||
while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) {
|
||||
$result = $result->wait($unwrap);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function wrapThenCallback(?callable $callback): ?callable
|
||||
{
|
||||
if (null === $callback) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return static function ($value) use ($callback) {
|
||||
return Create::promiseFor($callback($value));
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A test-friendly response.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class MockResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
use TransportResponseTrait {
|
||||
doDestruct as public __destruct;
|
||||
}
|
||||
|
||||
private $body;
|
||||
private $requestOptions = [];
|
||||
private $requestUrl;
|
||||
private $requestMethod;
|
||||
|
||||
private static $mainMulti;
|
||||
private static $idSequence = 0;
|
||||
|
||||
/**
|
||||
* @param string|string[]|iterable $body The response body as a string or an iterable of strings,
|
||||
* yielding an empty string simulates an idle timeout,
|
||||
* throwing an exception yields an ErrorChunk
|
||||
*
|
||||
* @see ResponseInterface::getInfo() for possible info, e.g. "response_headers"
|
||||
*/
|
||||
public function __construct($body = '', array $info = [])
|
||||
{
|
||||
$this->body = is_iterable($body) ? $body : (string) $body;
|
||||
$this->info = $info + ['http_code' => 200] + $this->info;
|
||||
|
||||
if (!isset($info['response_headers'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$responseHeaders = [];
|
||||
|
||||
foreach ($info['response_headers'] as $k => $v) {
|
||||
foreach ((array) $v as $v) {
|
||||
$responseHeaders[] = (\is_string($k) ? $k.': ' : '').$v;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info['response_headers'] = [];
|
||||
self::addResponseHeaders($responseHeaders, $this->info, $this->headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options used when doing the request.
|
||||
*/
|
||||
public function getRequestOptions(): array
|
||||
{
|
||||
return $this->requestOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL used when doing the request.
|
||||
*/
|
||||
public function getRequestUrl(): string
|
||||
{
|
||||
return $this->requestUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method used when doing the request.
|
||||
*/
|
||||
public function getRequestMethod(): string
|
||||
{
|
||||
return $this->requestMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
return null !== $type ? $this->info[$type] ?? null : $this->info;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
try {
|
||||
$this->body = null;
|
||||
} catch (TransportException $e) {
|
||||
// ignore errors when canceling
|
||||
}
|
||||
|
||||
$onProgress = $this->requestOptions['on_progress'] ?? static function () {};
|
||||
$dlSize = isset($this->headers['content-encoding']) || 'HEAD' === ($this->info['http_method'] ?? null) || \in_array($this->info['http_code'], [204, 304], true) ? 0 : (int) ($this->headers['content-length'][0] ?? 0);
|
||||
$onProgress($this->offset, $dlSize, $this->info);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function close(): void
|
||||
{
|
||||
$this->inflate = null;
|
||||
$this->body = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function fromRequest(string $method, string $url, array $options, ResponseInterface $mock): self
|
||||
{
|
||||
$response = new self([]);
|
||||
$response->requestOptions = $options;
|
||||
$response->id = ++self::$idSequence;
|
||||
$response->shouldBuffer = $options['buffer'] ?? true;
|
||||
$response->initializer = static function (self $response) {
|
||||
return \is_array($response->body[0] ?? null);
|
||||
};
|
||||
|
||||
$response->info['redirect_count'] = 0;
|
||||
$response->info['redirect_url'] = null;
|
||||
$response->info['start_time'] = microtime(true);
|
||||
$response->info['http_method'] = $method;
|
||||
$response->info['http_code'] = 0;
|
||||
$response->info['user_data'] = $options['user_data'] ?? null;
|
||||
$response->info['max_duration'] = $options['max_duration'] ?? null;
|
||||
$response->info['url'] = $url;
|
||||
|
||||
if ($mock instanceof self) {
|
||||
$mock->requestOptions = $response->requestOptions;
|
||||
$mock->requestMethod = $method;
|
||||
$mock->requestUrl = $url;
|
||||
}
|
||||
|
||||
self::writeRequest($response, $options, $mock);
|
||||
$response->body[] = [$options, $mock];
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (!$response->id) {
|
||||
throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.');
|
||||
}
|
||||
|
||||
$multi = self::$mainMulti ?? self::$mainMulti = new ClientState();
|
||||
|
||||
if (!isset($runningResponses[0])) {
|
||||
$runningResponses[0] = [$multi, []];
|
||||
}
|
||||
|
||||
$runningResponses[0][1][$response->id] = $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function perform(ClientState $multi, array &$responses): void
|
||||
{
|
||||
foreach ($responses as $response) {
|
||||
$id = $response->id;
|
||||
|
||||
if (null === $response->body) {
|
||||
// Canceled response
|
||||
$response->body = [];
|
||||
} elseif ([] === $response->body) {
|
||||
// Error chunk
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
} elseif (null === $chunk = array_shift($response->body)) {
|
||||
// Last chunk
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = array_shift($response->body);
|
||||
} elseif (\is_array($chunk)) {
|
||||
// First chunk
|
||||
try {
|
||||
$offset = 0;
|
||||
$chunk[1]->getStatusCode();
|
||||
$chunk[1]->getHeaders(false);
|
||||
self::readResponse($response, $chunk[0], $chunk[1], $offset);
|
||||
$multi->handlesActivity[$id][] = new FirstChunk();
|
||||
$buffer = $response->requestOptions['buffer'] ?? null;
|
||||
|
||||
if ($buffer instanceof \Closure && $response->content = $buffer($response->headers) ?: null) {
|
||||
$response->content = \is_resource($response->content) ? $response->content : fopen('php://temp', 'w+');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = $e;
|
||||
}
|
||||
} elseif ($chunk instanceof \Throwable) {
|
||||
$multi->handlesActivity[$id][] = null;
|
||||
$multi->handlesActivity[$id][] = $chunk;
|
||||
} else {
|
||||
// Data or timeout chunk
|
||||
$multi->handlesActivity[$id][] = $chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates sending the request.
|
||||
*/
|
||||
private static function writeRequest(self $response, array $options, ResponseInterface $mock)
|
||||
{
|
||||
$onProgress = $options['on_progress'] ?? static function () {};
|
||||
$response->info += $mock->getInfo() ?: [];
|
||||
|
||||
// simulate "size_upload" if it is set
|
||||
if (isset($response->info['size_upload'])) {
|
||||
$response->info['size_upload'] = 0.0;
|
||||
}
|
||||
|
||||
// simulate "total_time" if it is not set
|
||||
if (!isset($response->info['total_time'])) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
}
|
||||
|
||||
// "notify" DNS resolution
|
||||
$onProgress(0, 0, $response->info);
|
||||
|
||||
// consume the request body
|
||||
if (\is_resource($body = $options['body'] ?? '')) {
|
||||
$data = stream_get_contents($body);
|
||||
if (isset($response->info['size_upload'])) {
|
||||
$response->info['size_upload'] += \strlen($data);
|
||||
}
|
||||
} elseif ($body instanceof \Closure) {
|
||||
while ('' !== $data = $body(16372)) {
|
||||
if (!\is_string($data)) {
|
||||
throw new TransportException(sprintf('Return value of the "body" option callback must be string, "%s" returned.', get_debug_type($data)));
|
||||
}
|
||||
|
||||
// "notify" upload progress
|
||||
if (isset($response->info['size_upload'])) {
|
||||
$response->info['size_upload'] += \strlen($data);
|
||||
}
|
||||
|
||||
$onProgress(0, 0, $response->info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates reading the response.
|
||||
*/
|
||||
private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset)
|
||||
{
|
||||
$onProgress = $options['on_progress'] ?? static function () {};
|
||||
|
||||
// populate info related to headers
|
||||
$info = $mock->getInfo() ?: [];
|
||||
$response->info['http_code'] = ($info['http_code'] ?? 0) ?: $mock->getStatusCode() ?: 200;
|
||||
$response->addResponseHeaders($info['response_headers'] ?? [], $response->info, $response->headers);
|
||||
$dlSize = isset($response->headers['content-encoding']) || 'HEAD' === $response->info['http_method'] || \in_array($response->info['http_code'], [204, 304], true) ? 0 : (int) ($response->headers['content-length'][0] ?? 0);
|
||||
|
||||
$response->info = [
|
||||
'start_time' => $response->info['start_time'],
|
||||
'user_data' => $response->info['user_data'],
|
||||
'max_duration' => $response->info['max_duration'],
|
||||
'http_code' => $response->info['http_code'],
|
||||
] + $info + $response->info;
|
||||
|
||||
if (null !== $response->info['error']) {
|
||||
throw new TransportException($response->info['error']);
|
||||
}
|
||||
|
||||
if (!isset($response->info['total_time'])) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
}
|
||||
|
||||
// "notify" headers arrival
|
||||
$onProgress(0, $dlSize, $response->info);
|
||||
|
||||
// cast response body to activity list
|
||||
$body = $mock instanceof self ? $mock->body : $mock->getContent(false);
|
||||
|
||||
if (!\is_string($body)) {
|
||||
try {
|
||||
foreach ($body as $chunk) {
|
||||
if ('' === $chunk = (string) $chunk) {
|
||||
// simulate an idle timeout
|
||||
$response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url']));
|
||||
} else {
|
||||
$response->body[] = $chunk;
|
||||
$offset += \strlen($chunk);
|
||||
// "notify" download progress
|
||||
$onProgress($offset, $dlSize, $response->info);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$response->body[] = $e;
|
||||
}
|
||||
} elseif ('' !== $body) {
|
||||
$response->body[] = $body;
|
||||
$offset = \strlen($body);
|
||||
}
|
||||
|
||||
if (!isset($response->info['total_time'])) {
|
||||
$response->info['total_time'] = microtime(true) - $response->info['start_time'];
|
||||
}
|
||||
|
||||
// "notify" completion
|
||||
$onProgress($offset, $dlSize, $response->info);
|
||||
|
||||
if ($dlSize && $offset !== $dlSize) {
|
||||
throw new TransportException(sprintf('Transfer closed with %d bytes remaining to read.', $dlSize - $offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\Canary;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\ClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\NativeClientState;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class NativeResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
use CommonResponseTrait;
|
||||
use TransportResponseTrait;
|
||||
|
||||
private $context;
|
||||
private $url;
|
||||
private $resolver;
|
||||
private $onProgress;
|
||||
private $remaining;
|
||||
private $buffer;
|
||||
private $multi;
|
||||
private $pauseExpiry = 0;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(NativeClientState $multi, $context, string $url, array $options, array &$info, callable $resolver, ?callable $onProgress, ?LoggerInterface $logger)
|
||||
{
|
||||
$this->multi = $multi;
|
||||
$this->id = $id = (int) $context;
|
||||
$this->context = $context;
|
||||
$this->url = $url;
|
||||
$this->logger = $logger;
|
||||
$this->timeout = $options['timeout'];
|
||||
$this->info = &$info;
|
||||
$this->resolver = $resolver;
|
||||
$this->onProgress = $onProgress;
|
||||
$this->inflate = !isset($options['normalized_headers']['accept-encoding']);
|
||||
$this->shouldBuffer = $options['buffer'] ?? true;
|
||||
|
||||
// Temporary resource to dechunk the response stream
|
||||
$this->buffer = fopen('php://temp', 'w+');
|
||||
|
||||
$info['user_data'] = $options['user_data'];
|
||||
$info['max_duration'] = $options['max_duration'];
|
||||
++$multi->responseCount;
|
||||
|
||||
$this->initializer = static function (self $response) {
|
||||
return null === $response->remaining;
|
||||
};
|
||||
|
||||
$pauseExpiry = &$this->pauseExpiry;
|
||||
$info['pause_handler'] = static function (float $duration) use (&$pauseExpiry) {
|
||||
$pauseExpiry = 0 < $duration ? microtime(true) + $duration : 0;
|
||||
};
|
||||
|
||||
$this->canary = new Canary(static function () use ($multi, $id) {
|
||||
if (null !== ($host = $multi->openHandles[$id][6] ?? null) && 0 >= --$multi->hosts[$host]) {
|
||||
unset($multi->hosts[$host]);
|
||||
}
|
||||
unset($multi->openHandles[$id], $multi->handlesActivity[$id]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
if (!$info = $this->finalInfo) {
|
||||
$info = $this->info;
|
||||
$info['url'] = implode('', $info['url']);
|
||||
unset($info['size_body'], $info['request_header']);
|
||||
|
||||
if (null === $this->buffer) {
|
||||
$this->finalInfo = $info;
|
||||
}
|
||||
}
|
||||
|
||||
return null !== $type ? $info[$type] ?? null : $info;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
$this->doDestruct();
|
||||
} finally {
|
||||
// Clear the DNS cache when all requests completed
|
||||
if (0 >= --$this->multi->responseCount) {
|
||||
$this->multi->responseCount = 0;
|
||||
$this->multi->dnsCache = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function open(): void
|
||||
{
|
||||
$url = $this->url;
|
||||
|
||||
set_error_handler(function ($type, $msg) use (&$url) {
|
||||
if (\E_NOTICE !== $type || 'fopen(): Content-type not specified assuming application/x-www-form-urlencoded' !== $msg) {
|
||||
throw new TransportException($msg);
|
||||
}
|
||||
|
||||
$this->logger && $this->logger->info(sprintf('%s for "%s".', $msg, $url ?? $this->url));
|
||||
});
|
||||
|
||||
try {
|
||||
$this->info['start_time'] = microtime(true);
|
||||
|
||||
[$resolver, $url] = ($this->resolver)($this->multi);
|
||||
|
||||
while (true) {
|
||||
$context = stream_context_get_options($this->context);
|
||||
|
||||
if ($proxy = $context['http']['proxy'] ?? null) {
|
||||
$this->info['debug'] .= "* Establish HTTP proxy tunnel to {$proxy}\n";
|
||||
$this->info['request_header'] = $url;
|
||||
} else {
|
||||
$this->info['debug'] .= "* Trying {$this->info['primary_ip']}...\n";
|
||||
$this->info['request_header'] = $this->info['url']['path'].$this->info['url']['query'];
|
||||
}
|
||||
|
||||
$this->info['request_header'] = sprintf("> %s %s HTTP/%s \r\n", $context['http']['method'], $this->info['request_header'], $context['http']['protocol_version']);
|
||||
$this->info['request_header'] .= implode("\r\n", $context['http']['header'])."\r\n\r\n";
|
||||
|
||||
if (\array_key_exists('peer_name', $context['ssl']) && null === $context['ssl']['peer_name']) {
|
||||
unset($context['ssl']['peer_name']);
|
||||
$this->context = stream_context_create([], ['options' => $context] + stream_context_get_params($this->context));
|
||||
}
|
||||
|
||||
// Send request and follow redirects when needed
|
||||
$this->handle = $h = fopen($url, 'r', false, $this->context);
|
||||
self::addResponseHeaders(stream_get_meta_data($h)['wrapper_data'], $this->info, $this->headers, $this->info['debug']);
|
||||
$url = $resolver($this->multi, $this->headers['location'][0] ?? null, $this->context);
|
||||
|
||||
if (null === $url) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->logger && $this->logger->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->close();
|
||||
$this->multi->handlesActivity[$this->id][] = null;
|
||||
$this->multi->handlesActivity[$this->id][] = $e;
|
||||
|
||||
return;
|
||||
} finally {
|
||||
$this->info['pretransfer_time'] = $this->info['total_time'] = microtime(true) - $this->info['start_time'];
|
||||
restore_error_handler();
|
||||
}
|
||||
|
||||
if (isset($context['ssl']['capture_peer_cert_chain']) && isset(($context = stream_context_get_options($this->context))['ssl']['peer_certificate_chain'])) {
|
||||
$this->info['peer_certificate_chain'] = $context['ssl']['peer_certificate_chain'];
|
||||
}
|
||||
|
||||
stream_set_blocking($h, false);
|
||||
$this->context = $this->resolver = null;
|
||||
|
||||
// Create dechunk buffers
|
||||
if (isset($this->headers['content-length'])) {
|
||||
$this->remaining = (int) $this->headers['content-length'][0];
|
||||
} elseif ('chunked' === ($this->headers['transfer-encoding'][0] ?? null)) {
|
||||
stream_filter_append($this->buffer, 'dechunk', \STREAM_FILTER_WRITE);
|
||||
$this->remaining = -1;
|
||||
} else {
|
||||
$this->remaining = -2;
|
||||
}
|
||||
|
||||
$this->multi->handlesActivity[$this->id] = [new FirstChunk()];
|
||||
|
||||
if ('HEAD' === $context['http']['method'] || \in_array($this->info['http_code'], [204, 304], true)) {
|
||||
$this->multi->handlesActivity[$this->id][] = null;
|
||||
$this->multi->handlesActivity[$this->id][] = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$host = parse_url($this->info['redirect_url'] ?? $this->url, \PHP_URL_HOST);
|
||||
$this->multi->lastTimeout = null;
|
||||
$this->multi->openHandles[$this->id] = [&$this->pauseExpiry, $h, $this->buffer, $this->onProgress, &$this->remaining, &$this->info, $host];
|
||||
$this->multi->hosts[$host] = 1 + ($this->multi->hosts[$host] ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private function close(): void
|
||||
{
|
||||
$this->canary->cancel();
|
||||
$this->handle = $this->buffer = $this->inflate = $this->onProgress = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
private static function schedule(self $response, array &$runningResponses): void
|
||||
{
|
||||
if (!isset($runningResponses[$i = $response->multi->id])) {
|
||||
$runningResponses[$i] = [$response->multi, []];
|
||||
}
|
||||
|
||||
$runningResponses[$i][1][$response->id] = $response;
|
||||
|
||||
if (null === $response->buffer) {
|
||||
// Response already completed
|
||||
$response->multi->handlesActivity[$response->id][] = null;
|
||||
$response->multi->handlesActivity[$response->id][] = null !== $response->info['error'] ? new TransportException($response->info['error']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param NativeClientState $multi
|
||||
*/
|
||||
private static function perform(ClientState $multi, ?array &$responses = null): void
|
||||
{
|
||||
foreach ($multi->openHandles as $i => [$pauseExpiry, $h, $buffer, $onProgress]) {
|
||||
if ($pauseExpiry) {
|
||||
if (microtime(true) < $pauseExpiry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$multi->openHandles[$i][0] = 0;
|
||||
}
|
||||
|
||||
$hasActivity = false;
|
||||
$remaining = &$multi->openHandles[$i][4];
|
||||
$info = &$multi->openHandles[$i][5];
|
||||
$e = null;
|
||||
|
||||
// Read incoming buffer and write it to the dechunk one
|
||||
try {
|
||||
if ($remaining && '' !== $data = (string) fread($h, 0 > $remaining ? 16372 : $remaining)) {
|
||||
fwrite($buffer, $data);
|
||||
$hasActivity = true;
|
||||
$multi->sleep = false;
|
||||
|
||||
if (-1 !== $remaining) {
|
||||
$remaining -= \strlen($data);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$hasActivity = $onProgress = false;
|
||||
}
|
||||
|
||||
if (!$hasActivity) {
|
||||
if ($onProgress) {
|
||||
try {
|
||||
// Notify the progress callback so that it can e.g. cancel
|
||||
// the request if the stream is inactive for too long
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
$onProgress();
|
||||
} catch (\Throwable $e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
} elseif ('' !== $data = stream_get_contents($buffer, -1, 0)) {
|
||||
rewind($buffer);
|
||||
ftruncate($buffer, 0);
|
||||
|
||||
if (null === $e) {
|
||||
$multi->handlesActivity[$i][] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== $e || !$remaining || feof($h)) {
|
||||
// Stream completed
|
||||
$info['total_time'] = microtime(true) - $info['start_time'];
|
||||
$info['starttransfer_time'] = $info['starttransfer_time'] ?: $info['total_time'];
|
||||
|
||||
if ($onProgress) {
|
||||
try {
|
||||
$onProgress(-1);
|
||||
} catch (\Throwable $e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $e) {
|
||||
if (0 < $remaining) {
|
||||
$e = new TransportException(sprintf('Transfer closed with %s bytes remaining to read.', $remaining));
|
||||
} elseif (-1 === $remaining && fwrite($buffer, '-') && '' !== stream_get_contents($buffer, -1, 0)) {
|
||||
$e = new TransportException('Transfer closed with outstanding data remaining from chunked response.');
|
||||
}
|
||||
}
|
||||
|
||||
$multi->handlesActivity[$i][] = null;
|
||||
$multi->handlesActivity[$i][] = $e;
|
||||
if (null !== ($host = $multi->openHandles[$i][6] ?? null) && 0 >= --$multi->hosts[$host]) {
|
||||
unset($multi->hosts[$host]);
|
||||
}
|
||||
unset($multi->openHandles[$i]);
|
||||
$multi->sleep = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $responses) {
|
||||
return;
|
||||
}
|
||||
|
||||
$maxHosts = $multi->maxHostConnections;
|
||||
|
||||
foreach ($responses as $i => $response) {
|
||||
if (null !== $response->remaining || null === $response->buffer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($response->pauseExpiry && microtime(true) < $response->pauseExpiry) {
|
||||
// Create empty open handles to tell we still have pending requests
|
||||
$multi->openHandles[$i] = [\INF, null, null, null];
|
||||
} elseif ($maxHosts && $maxHosts > ($multi->hosts[parse_url($response->url, \PHP_URL_HOST)] ?? 0)) {
|
||||
// Open the next pending request - this is a blocking operation so we do only one of them
|
||||
$response->open();
|
||||
$multi->sleep = false;
|
||||
self::perform($multi);
|
||||
$maxHosts = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param NativeClientState $multi
|
||||
*/
|
||||
private static function select(ClientState $multi, float $timeout): int
|
||||
{
|
||||
if (!$multi->sleep = !$multi->sleep) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$_ = $handles = [];
|
||||
$now = null;
|
||||
|
||||
foreach ($multi->openHandles as [$pauseExpiry, $h]) {
|
||||
if (null === $h) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($pauseExpiry && ($now ?? $now = microtime(true)) < $pauseExpiry) {
|
||||
$timeout = min($timeout, $pauseExpiry - $now);
|
||||
continue;
|
||||
}
|
||||
|
||||
$handles[] = $h;
|
||||
}
|
||||
|
||||
if (!$handles) {
|
||||
usleep((int) (1E6 * $timeout));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return stream_select($handles, $_, $_, (int) $timeout, (int) (1E6 * ($timeout - (int) $timeout)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
final class ResponseStream implements ResponseStreamInterface
|
||||
{
|
||||
private $generator;
|
||||
|
||||
public function __construct(\Generator $generator)
|
||||
{
|
||||
$this->generator = $generator;
|
||||
}
|
||||
|
||||
public function key(): ResponseInterface
|
||||
{
|
||||
return $this->generator->key();
|
||||
}
|
||||
|
||||
public function current(): ChunkInterface
|
||||
{
|
||||
return $this->generator->current();
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$this->generator->next();
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->generator->rewind();
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return $this->generator->valid();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Allows turning ResponseInterface instances to PHP streams.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
class StreamWrapper
|
||||
{
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/** @var HttpClientInterface */
|
||||
private $client;
|
||||
|
||||
/** @var ResponseInterface */
|
||||
private $response;
|
||||
|
||||
/** @var resource|string|null */
|
||||
private $content;
|
||||
|
||||
/** @var resource|null */
|
||||
private $handle;
|
||||
|
||||
private $blocking = true;
|
||||
private $timeout;
|
||||
private $eof = false;
|
||||
private $offset = 0;
|
||||
|
||||
/**
|
||||
* Creates a PHP stream resource from a ResponseInterface.
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public static function createResource(ResponseInterface $response, ?HttpClientInterface $client = null)
|
||||
{
|
||||
if ($response instanceof StreamableInterface) {
|
||||
$stack = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 2);
|
||||
|
||||
if ($response !== ($stack[1]['object'] ?? null)) {
|
||||
return $response->toStream(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (null === $client && !method_exists($response, 'stream')) {
|
||||
throw new \InvalidArgumentException(sprintf('Providing a client to "%s()" is required when the response doesn\'t have any "stream()" method.', __CLASS__));
|
||||
}
|
||||
|
||||
static $registered = false;
|
||||
|
||||
if (!$registered = $registered || stream_wrapper_register(strtr(__CLASS__, '\\', '-'), __CLASS__)) {
|
||||
throw new \RuntimeException(error_get_last()['message'] ?? 'Registering the "symfony" stream wrapper failed.');
|
||||
}
|
||||
|
||||
$context = [
|
||||
'client' => $client ?? $response,
|
||||
'response' => $response,
|
||||
];
|
||||
|
||||
return fopen(strtr(__CLASS__, '\\', '-').'://'.$response->getInfo('url'), 'r', false, stream_context_create(['symfony' => $context]));
|
||||
}
|
||||
|
||||
public function getResponse(): ResponseInterface
|
||||
{
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|callable|null $handle The resource handle that should be monitored when
|
||||
* stream_select() is used on the created stream
|
||||
* @param resource|null $content The seekable resource where the response body is buffered
|
||||
*/
|
||||
public function bindHandles(&$handle, &$content): void
|
||||
{
|
||||
$this->handle = &$handle;
|
||||
$this->content = &$content;
|
||||
$this->offset = null;
|
||||
}
|
||||
|
||||
public function stream_open(string $path, string $mode, int $options): bool
|
||||
{
|
||||
if ('r' !== $mode) {
|
||||
if ($options & \STREAM_REPORT_ERRORS) {
|
||||
trigger_error(sprintf('Invalid mode "%s": only "r" is supported.', $mode), \E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$context = stream_context_get_options($this->context)['symfony'] ?? null;
|
||||
$this->client = $context['client'] ?? null;
|
||||
$this->response = $context['response'] ?? null;
|
||||
$this->context = null;
|
||||
|
||||
if (null !== $this->client && null !== $this->response) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($options & \STREAM_REPORT_ERRORS) {
|
||||
trigger_error('Missing options "client" or "response" in "symfony" stream context.', \E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_read(int $count)
|
||||
{
|
||||
if (\is_resource($this->content)) {
|
||||
// Empty the internal activity list
|
||||
foreach ($this->client->stream([$this->response], 0) as $chunk) {
|
||||
try {
|
||||
if (!$chunk->isTimeout() && $chunk->isFirst()) {
|
||||
$this->response->getStatusCode(); // ignore 3/4/5xx
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 !== fseek($this->content, $this->offset ?? 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ('' !== $data = fread($this->content, $count)) {
|
||||
fseek($this->content, 0, \SEEK_END);
|
||||
$this->offset += \strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_string($this->content)) {
|
||||
if (\strlen($this->content) <= $count) {
|
||||
$data = $this->content;
|
||||
$this->content = null;
|
||||
} else {
|
||||
$data = substr($this->content, 0, $count);
|
||||
$this->content = substr($this->content, $count);
|
||||
}
|
||||
$this->offset += \strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
foreach ($this->client->stream([$this->response], $this->blocking ? $this->timeout : 0) as $chunk) {
|
||||
try {
|
||||
$this->eof = true;
|
||||
$this->eof = !$chunk->isTimeout();
|
||||
|
||||
if (!$this->eof && !$this->blocking) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->eof = $chunk->isLast();
|
||||
|
||||
if ($chunk->isFirst()) {
|
||||
$this->response->getStatusCode(); // ignore 3/4/5xx
|
||||
}
|
||||
|
||||
if ('' !== $data = $chunk->getContent()) {
|
||||
if (\strlen($data) > $count) {
|
||||
if (null === $this->content) {
|
||||
$this->content = substr($data, $count);
|
||||
}
|
||||
$data = substr($data, 0, $count);
|
||||
}
|
||||
$this->offset += \strlen($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
|
||||
{
|
||||
if (\STREAM_OPTION_BLOCKING === $option) {
|
||||
$this->blocking = (bool) $arg1;
|
||||
} elseif (\STREAM_OPTION_READ_TIMEOUT === $option) {
|
||||
$this->timeout = $arg1 + $arg2 / 1e6;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_tell(): int
|
||||
{
|
||||
return $this->offset ?? 0;
|
||||
}
|
||||
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return $this->eof && !\is_string($this->content);
|
||||
}
|
||||
|
||||
public function stream_seek(int $offset, int $whence = \SEEK_SET): bool
|
||||
{
|
||||
if (null === $this->content && null === $this->offset) {
|
||||
$this->response->getStatusCode();
|
||||
$this->offset = 0;
|
||||
}
|
||||
|
||||
if (!\is_resource($this->content) || 0 !== fseek($this->content, 0, \SEEK_END)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$size = ftell($this->content);
|
||||
|
||||
if (\SEEK_CUR === $whence) {
|
||||
$offset += $this->offset ?? 0;
|
||||
}
|
||||
|
||||
if (\SEEK_END === $whence || $size < $offset) {
|
||||
foreach ($this->client->stream([$this->response]) as $chunk) {
|
||||
try {
|
||||
if ($chunk->isFirst()) {
|
||||
$this->response->getStatusCode(); // ignore 3/4/5xx
|
||||
}
|
||||
|
||||
// Chunks are buffered in $this->content already
|
||||
$size += \strlen($chunk->getContent());
|
||||
|
||||
if (\SEEK_END !== $whence && $offset <= $size) {
|
||||
break;
|
||||
}
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (\SEEK_END === $whence) {
|
||||
$offset += $size;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 <= $offset && $offset <= $size) {
|
||||
$this->eof = false;
|
||||
$this->offset = $offset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_cast(int $castAs)
|
||||
{
|
||||
if (\STREAM_CAST_FOR_SELECT === $castAs) {
|
||||
$this->response->getHeaders(false);
|
||||
|
||||
return (\is_callable($this->handle) ? ($this->handle)() : $this->handle) ?? false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_stat(): array
|
||||
{
|
||||
try {
|
||||
$headers = $this->response->getHeaders(false);
|
||||
} catch (ExceptionInterface $e) {
|
||||
trigger_error($e->getMessage(), \E_USER_WARNING);
|
||||
$headers = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'dev' => 0,
|
||||
'ino' => 0,
|
||||
'mode' => 33060,
|
||||
'nlink' => 0,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'rdev' => 0,
|
||||
'size' => (int) ($headers['content-length'][0] ?? -1),
|
||||
'atime' => 0,
|
||||
'mtime' => strtotime($headers['last-modified'][0] ?? '') ?: 0,
|
||||
'ctime' => 0,
|
||||
'blksize' => 0,
|
||||
'blocks' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface StreamableInterface
|
||||
{
|
||||
/**
|
||||
* Casts the response to a PHP stream resource.
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @throws TransportExceptionInterface When a network error occurs
|
||||
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
|
||||
* @throws ClientExceptionInterface On a 4xx when $throw is true
|
||||
* @throws ServerExceptionInterface On a 5xx when $throw is true
|
||||
*/
|
||||
public function toStream(bool $throw = true);
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\ClientException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\RedirectionException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\ServerException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\TraceableHttpClient;
|
||||
use Symfony\Component\Stopwatch\StopwatchEvent;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class TraceableResponse implements ResponseInterface, StreamableInterface
|
||||
{
|
||||
private $client;
|
||||
private $response;
|
||||
private $content;
|
||||
private $event;
|
||||
|
||||
public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content, ?StopwatchEvent $event = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->response = $response;
|
||||
$this->content = &$content;
|
||||
$this->event = $event;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
try {
|
||||
if (method_exists($this->response, '__destruct')) {
|
||||
$this->response->__destruct();
|
||||
}
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
try {
|
||||
return $this->response->getStatusCode();
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->lap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getHeaders(bool $throw = true): array
|
||||
{
|
||||
try {
|
||||
return $this->response->getHeaders($throw);
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->lap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getContent(bool $throw = true): string
|
||||
{
|
||||
try {
|
||||
if (false === $this->content) {
|
||||
return $this->response->getContent($throw);
|
||||
}
|
||||
|
||||
return $this->content = $this->response->getContent(false);
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
if ($throw) {
|
||||
$this->checkStatusCode($this->response->getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function toArray(bool $throw = true): array
|
||||
{
|
||||
try {
|
||||
if (false === $this->content) {
|
||||
return $this->response->toArray($throw);
|
||||
}
|
||||
|
||||
return $this->content = $this->response->toArray(false);
|
||||
} finally {
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
if ($throw) {
|
||||
$this->checkStatusCode($this->response->getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->response->cancel();
|
||||
|
||||
if ($this->event && $this->event->isStarted()) {
|
||||
$this->event->stop();
|
||||
}
|
||||
}
|
||||
|
||||
public function getInfo(?string $type = null)
|
||||
{
|
||||
return $this->response->getInfo($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts the response to a PHP stream resource.
|
||||
*
|
||||
* @return resource
|
||||
*
|
||||
* @throws TransportExceptionInterface When a network error occurs
|
||||
* @throws RedirectionExceptionInterface On a 3xx when $throw is true and the "max_redirects" option has been reached
|
||||
* @throws ClientExceptionInterface On a 4xx when $throw is true
|
||||
* @throws ServerExceptionInterface On a 5xx when $throw is true
|
||||
*/
|
||||
public function toStream(bool $throw = true)
|
||||
{
|
||||
if ($throw) {
|
||||
// Ensure headers arrived
|
||||
$this->response->getHeaders(true);
|
||||
}
|
||||
|
||||
if ($this->response instanceof StreamableInterface) {
|
||||
return $this->response->toStream(false);
|
||||
}
|
||||
|
||||
return StreamWrapper::createResource($this->response, $this->client);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function stream(HttpClientInterface $client, iterable $responses, ?float $timeout): \Generator
|
||||
{
|
||||
$wrappedResponses = [];
|
||||
$traceableMap = new \SplObjectStorage();
|
||||
|
||||
foreach ($responses as $r) {
|
||||
if (!$r instanceof self) {
|
||||
throw new \TypeError(sprintf('"%s::stream()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', TraceableHttpClient::class, get_debug_type($r)));
|
||||
}
|
||||
|
||||
$traceableMap[$r->response] = $r;
|
||||
$wrappedResponses[] = $r->response;
|
||||
if ($r->event && !$r->event->isStarted()) {
|
||||
$r->event->start();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($client->stream($wrappedResponses, $timeout) as $r => $chunk) {
|
||||
if ($traceableMap[$r]->event && $traceableMap[$r]->event->isStarted()) {
|
||||
try {
|
||||
if ($chunk->isTimeout() || !$chunk->isLast()) {
|
||||
$traceableMap[$r]->event->lap();
|
||||
} else {
|
||||
$traceableMap[$r]->event->stop();
|
||||
}
|
||||
} catch (TransportExceptionInterface $e) {
|
||||
$traceableMap[$r]->event->stop();
|
||||
if ($chunk instanceof ErrorChunk) {
|
||||
$chunk->didThrow(false);
|
||||
} else {
|
||||
$chunk = new ErrorChunk($chunk->getOffset(), $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
yield $traceableMap[$r] => $chunk;
|
||||
}
|
||||
}
|
||||
|
||||
private function checkStatusCode(int $code)
|
||||
{
|
||||
if (500 <= $code) {
|
||||
throw new ServerException($this);
|
||||
}
|
||||
|
||||
if (400 <= $code) {
|
||||
throw new ClientException($this);
|
||||
}
|
||||
|
||||
if (300 <= $code) {
|
||||
throw new RedirectionException($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\DataChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\ErrorChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\FirstChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Chunk\LastChunk;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\TransportException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Internal\ClientState;
|
||||
|
||||
/**
|
||||
* Implements common logic for transport-level response classes.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait TransportResponseTrait
|
||||
{
|
||||
private $canary;
|
||||
private $headers = [];
|
||||
private $info = [
|
||||
'response_headers' => [],
|
||||
'http_code' => 0,
|
||||
'error' => null,
|
||||
'canceled' => false,
|
||||
];
|
||||
|
||||
/** @var object|resource */
|
||||
private $handle;
|
||||
private $id;
|
||||
private $timeout = 0;
|
||||
private $inflate;
|
||||
private $finalInfo;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
return $this->info['http_code'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHeaders(bool $throw = true): array
|
||||
{
|
||||
if ($this->initializer) {
|
||||
self::initialize($this);
|
||||
}
|
||||
|
||||
if ($throw) {
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function cancel(): void
|
||||
{
|
||||
$this->info['canceled'] = true;
|
||||
$this->info['error'] = 'Response has been canceled.';
|
||||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the response and all its network handles.
|
||||
*/
|
||||
protected function close(): void
|
||||
{
|
||||
$this->canary->cancel();
|
||||
$this->inflate = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds pending responses to the activity list.
|
||||
*/
|
||||
abstract protected static function schedule(self $response, array &$runningResponses): void;
|
||||
|
||||
/**
|
||||
* Performs all pending non-blocking operations.
|
||||
*/
|
||||
abstract protected static function perform(ClientState $multi, array &$responses): void;
|
||||
|
||||
/**
|
||||
* Waits for network activity.
|
||||
*/
|
||||
abstract protected static function select(ClientState $multi, float $timeout): int;
|
||||
|
||||
private static function addResponseHeaders(array $responseHeaders, array &$info, array &$headers, string &$debug = ''): void
|
||||
{
|
||||
foreach ($responseHeaders as $h) {
|
||||
if (11 <= \strlen($h) && '/' === $h[4] && preg_match('#^HTTP/\d+(?:\.\d+)? (\d\d\d)(?: |$)#', $h, $m)) {
|
||||
if ($headers) {
|
||||
$debug .= "< \r\n";
|
||||
$headers = [];
|
||||
}
|
||||
$info['http_code'] = (int) $m[1];
|
||||
} elseif (2 === \count($m = explode(':', $h, 2))) {
|
||||
$headers[strtolower($m[0])][] = ltrim($m[1]);
|
||||
}
|
||||
|
||||
$debug .= "< {$h}\r\n";
|
||||
$info['response_headers'][] = $h;
|
||||
}
|
||||
|
||||
$debug .= "< \r\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the request is always sent and that the response code was checked.
|
||||
*/
|
||||
private function doDestruct()
|
||||
{
|
||||
$this->shouldBuffer = true;
|
||||
|
||||
if ($this->initializer && null === $this->info['error']) {
|
||||
self::initialize($this);
|
||||
$this->checkStatusCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements an event loop based on a buffer activity queue.
|
||||
*
|
||||
* @param iterable<array-key, self> $responses
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function stream(iterable $responses, ?float $timeout = null): \Generator
|
||||
{
|
||||
$runningResponses = [];
|
||||
|
||||
foreach ($responses as $response) {
|
||||
self::schedule($response, $runningResponses);
|
||||
}
|
||||
|
||||
$lastActivity = microtime(true);
|
||||
$elapsedTimeout = 0;
|
||||
|
||||
if ($fromLastTimeout = 0.0 === $timeout && '-0' === (string) $timeout) {
|
||||
$timeout = null;
|
||||
} elseif ($fromLastTimeout = 0 > $timeout) {
|
||||
$timeout = -$timeout;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
$hasActivity = false;
|
||||
$timeoutMax = 0;
|
||||
$timeoutMin = $timeout ?? \INF;
|
||||
|
||||
/** @var ClientState $multi */
|
||||
foreach ($runningResponses as $i => [$multi]) {
|
||||
$responses = &$runningResponses[$i][1];
|
||||
self::perform($multi, $responses);
|
||||
|
||||
foreach ($responses as $j => $response) {
|
||||
$timeoutMax = $timeout ?? max($timeoutMax, $response->timeout);
|
||||
$timeoutMin = min($timeoutMin, $response->timeout, 1);
|
||||
$chunk = false;
|
||||
|
||||
if ($fromLastTimeout && null !== $multi->lastTimeout) {
|
||||
$elapsedTimeout = microtime(true) - $multi->lastTimeout;
|
||||
}
|
||||
|
||||
if (isset($multi->handlesActivity[$j])) {
|
||||
$multi->lastTimeout = null;
|
||||
} elseif (!isset($multi->openHandles[$j])) {
|
||||
unset($responses[$j]);
|
||||
continue;
|
||||
} elseif ($elapsedTimeout >= $timeoutMax) {
|
||||
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))];
|
||||
$multi->lastTimeout ?? $multi->lastTimeout = $lastActivity;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
while ($multi->handlesActivity[$j] ?? false) {
|
||||
$hasActivity = true;
|
||||
$elapsedTimeout = 0;
|
||||
|
||||
if (\is_string($chunk = array_shift($multi->handlesActivity[$j]))) {
|
||||
if (null !== $response->inflate && false === $chunk = @inflate_add($response->inflate, $chunk)) {
|
||||
$multi->handlesActivity[$j] = [null, new TransportException(sprintf('Error while processing content unencoding for "%s".', $response->getInfo('url')))];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' !== $chunk && null !== $response->content && \strlen($chunk) !== fwrite($response->content, $chunk)) {
|
||||
$multi->handlesActivity[$j] = [null, new TransportException(sprintf('Failed writing %d bytes to the response buffer.', \strlen($chunk)))];
|
||||
continue;
|
||||
}
|
||||
|
||||
$chunkLen = \strlen($chunk);
|
||||
$chunk = new DataChunk($response->offset, $chunk);
|
||||
$response->offset += $chunkLen;
|
||||
} elseif (null === $chunk) {
|
||||
$e = $multi->handlesActivity[$j][0];
|
||||
unset($responses[$j], $multi->handlesActivity[$j]);
|
||||
$response->close();
|
||||
|
||||
if (null !== $e) {
|
||||
$response->info['error'] = $e->getMessage();
|
||||
|
||||
if ($e instanceof \Error) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$chunk = new ErrorChunk($response->offset, $e);
|
||||
} else {
|
||||
if (0 === $response->offset && null === $response->content) {
|
||||
$response->content = fopen('php://memory', 'w+');
|
||||
}
|
||||
|
||||
$chunk = new LastChunk($response->offset);
|
||||
}
|
||||
} elseif ($chunk instanceof ErrorChunk) {
|
||||
unset($responses[$j]);
|
||||
$elapsedTimeout = $timeoutMax;
|
||||
} elseif ($chunk instanceof FirstChunk) {
|
||||
if ($response->logger) {
|
||||
$info = $response->getInfo();
|
||||
$response->logger->info(sprintf('Response: "%s %s"', $info['http_code'], $info['url']));
|
||||
}
|
||||
|
||||
$response->inflate = \extension_loaded('zlib') && $response->inflate && 'gzip' === ($response->headers['content-encoding'][0] ?? null) ? inflate_init(\ZLIB_ENCODING_GZIP) : null;
|
||||
|
||||
if ($response->shouldBuffer instanceof \Closure) {
|
||||
try {
|
||||
$response->shouldBuffer = ($response->shouldBuffer)($response->headers);
|
||||
|
||||
if (null !== $response->info['error']) {
|
||||
throw new TransportException($response->info['error']);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$response->close();
|
||||
$multi->handlesActivity[$j] = [null, $e];
|
||||
}
|
||||
}
|
||||
|
||||
if (true === $response->shouldBuffer) {
|
||||
$response->content = fopen('php://temp', 'w+');
|
||||
} elseif (\is_resource($response->shouldBuffer)) {
|
||||
$response->content = $response->shouldBuffer;
|
||||
}
|
||||
$response->shouldBuffer = null;
|
||||
|
||||
yield $response => $chunk;
|
||||
|
||||
if ($response->initializer && null === $response->info['error']) {
|
||||
// Ensure the HTTP status code is always checked
|
||||
$response->getHeaders(true);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
yield $response => $chunk;
|
||||
}
|
||||
|
||||
unset($multi->handlesActivity[$j]);
|
||||
|
||||
if ($chunk instanceof ErrorChunk && !$chunk->didThrow()) {
|
||||
// Ensure transport exceptions are always thrown
|
||||
$chunk->getContent();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$responses) {
|
||||
unset($runningResponses[$i]);
|
||||
}
|
||||
|
||||
// Prevent memory leaks
|
||||
$multi->handlesActivity = $multi->handlesActivity ?: [];
|
||||
$multi->openHandles = $multi->openHandles ?: [];
|
||||
}
|
||||
|
||||
if (!$runningResponses) {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($hasActivity) {
|
||||
$lastActivity = microtime(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (-1 === self::select($multi, min($timeoutMin, $timeoutMax - $elapsedTimeout))) {
|
||||
usleep((int) min(500, 1E6 * $timeoutMin));
|
||||
}
|
||||
|
||||
$elapsedTimeout = microtime(true) - $lastActivity;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Retry;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncContext;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* Decides to retry the request when HTTP status codes belong to the given list of codes.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class GenericRetryStrategy implements RetryStrategyInterface
|
||||
{
|
||||
public const IDEMPOTENT_METHODS = ['GET', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE'];
|
||||
public const DEFAULT_RETRY_STATUS_CODES = [
|
||||
0 => self::IDEMPOTENT_METHODS, // for transport exceptions
|
||||
423,
|
||||
425,
|
||||
429,
|
||||
500 => self::IDEMPOTENT_METHODS,
|
||||
502,
|
||||
503,
|
||||
504 => self::IDEMPOTENT_METHODS,
|
||||
507 => self::IDEMPOTENT_METHODS,
|
||||
510 => self::IDEMPOTENT_METHODS,
|
||||
];
|
||||
|
||||
private $statusCodes;
|
||||
private $delayMs;
|
||||
private $multiplier;
|
||||
private $maxDelayMs;
|
||||
private $jitter;
|
||||
|
||||
/**
|
||||
* @param array $statusCodes List of HTTP status codes that trigger a retry
|
||||
* @param int $delayMs Amount of time to delay (or the initial value when multiplier is used)
|
||||
* @param float $multiplier Multiplier to apply to the delay each time a retry occurs
|
||||
* @param int $maxDelayMs Maximum delay to allow (0 means no maximum)
|
||||
* @param float $jitter Probability of randomness int delay (0 = none, 1 = 100% random)
|
||||
*/
|
||||
public function __construct(array $statusCodes = self::DEFAULT_RETRY_STATUS_CODES, int $delayMs = 1000, float $multiplier = 2.0, int $maxDelayMs = 0, float $jitter = 0.1)
|
||||
{
|
||||
$this->statusCodes = $statusCodes;
|
||||
|
||||
if ($delayMs < 0) {
|
||||
throw new InvalidArgumentException(sprintf('Delay must be greater than or equal to zero: "%s" given.', $delayMs));
|
||||
}
|
||||
$this->delayMs = $delayMs;
|
||||
|
||||
if ($multiplier < 1) {
|
||||
throw new InvalidArgumentException(sprintf('Multiplier must be greater than or equal to one: "%s" given.', $multiplier));
|
||||
}
|
||||
$this->multiplier = $multiplier;
|
||||
|
||||
if ($maxDelayMs < 0) {
|
||||
throw new InvalidArgumentException(sprintf('Max delay must be greater than or equal to zero: "%s" given.', $maxDelayMs));
|
||||
}
|
||||
$this->maxDelayMs = $maxDelayMs;
|
||||
|
||||
if ($jitter < 0 || $jitter > 1) {
|
||||
throw new InvalidArgumentException(sprintf('Jitter must be between 0 and 1: "%s" given.', $jitter));
|
||||
}
|
||||
$this->jitter = $jitter;
|
||||
}
|
||||
|
||||
public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool
|
||||
{
|
||||
$statusCode = $context->getStatusCode();
|
||||
if (\in_array($statusCode, $this->statusCodes, true)) {
|
||||
return true;
|
||||
}
|
||||
if (isset($this->statusCodes[$statusCode]) && \is_array($this->statusCodes[$statusCode])) {
|
||||
return \in_array($context->getInfo('http_method'), $this->statusCodes[$statusCode], true);
|
||||
}
|
||||
if (null === $exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\in_array(0, $this->statusCodes, true)) {
|
||||
return true;
|
||||
}
|
||||
if (isset($this->statusCodes[0]) && \is_array($this->statusCodes[0])) {
|
||||
return \in_array($context->getInfo('http_method'), $this->statusCodes[0], true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int
|
||||
{
|
||||
$delay = $this->delayMs * $this->multiplier ** $context->getInfo('retry_count');
|
||||
|
||||
if ($this->jitter > 0) {
|
||||
$randomness = (int) ($delay * $this->jitter);
|
||||
$delay = $delay + random_int(-$randomness, +$randomness);
|
||||
}
|
||||
|
||||
if ($delay > $this->maxDelayMs && 0 !== $this->maxDelayMs) {
|
||||
return $this->maxDelayMs;
|
||||
}
|
||||
|
||||
return (int) $delay;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Retry;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncContext;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface RetryStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Returns whether the request should be retried.
|
||||
*
|
||||
* @param ?string $responseContent Null is passed when the body did not arrive yet
|
||||
*
|
||||
* @return bool|null Returns null to signal that the body is required to take a decision
|
||||
*/
|
||||
public function shouldRetry(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): ?bool;
|
||||
|
||||
/**
|
||||
* Returns the time to wait in milliseconds.
|
||||
*/
|
||||
public function getDelay(AsyncContext $context, ?string $responseContent, ?TransportExceptionInterface $exception): int;
|
||||
}
|
||||
171
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/RetryableHttpClient.php
vendored
Normal file
171
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/RetryableHttpClient.php
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\NullLogger;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncContext;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\AsyncResponse;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Retry\GenericRetryStrategy;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Retry\RetryStrategyInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ChunkInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Automatically retries failing HTTP requests.
|
||||
*
|
||||
* @author Jérémy Derussé <jeremy@derusse.com>
|
||||
*/
|
||||
class RetryableHttpClient implements HttpClientInterface, ResetInterface
|
||||
{
|
||||
use AsyncDecoratorTrait;
|
||||
|
||||
private $strategy;
|
||||
private $maxRetries;
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @param int $maxRetries The maximum number of times to retry
|
||||
*/
|
||||
public function __construct(HttpClientInterface $client, ?RetryStrategyInterface $strategy = null, int $maxRetries = 3, ?LoggerInterface $logger = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->strategy = $strategy ?? new GenericRetryStrategy();
|
||||
$this->maxRetries = $maxRetries;
|
||||
$this->logger = $logger ?? new NullLogger();
|
||||
}
|
||||
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
if ($this->maxRetries <= 0) {
|
||||
return new AsyncResponse($this->client, $method, $url, $options);
|
||||
}
|
||||
|
||||
$retryCount = 0;
|
||||
$content = '';
|
||||
$firstChunk = null;
|
||||
|
||||
return new AsyncResponse($this->client, $method, $url, $options, function (ChunkInterface $chunk, AsyncContext $context) use ($method, $url, $options, &$retryCount, &$content, &$firstChunk) {
|
||||
$exception = null;
|
||||
try {
|
||||
if ($context->getInfo('canceled') || $chunk->isTimeout() || null !== $chunk->getInformationalStatus()) {
|
||||
yield $chunk;
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (TransportExceptionInterface $exception) {
|
||||
// catch TransportExceptionInterface to send it to the strategy
|
||||
}
|
||||
if (null !== $exception) {
|
||||
// always retry request that fail to resolve DNS
|
||||
if ('' !== $context->getInfo('primary_ip')) {
|
||||
$shouldRetry = $this->strategy->shouldRetry($context, null, $exception);
|
||||
if (null === $shouldRetry) {
|
||||
throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with an exception.', \get_class($this->strategy)));
|
||||
}
|
||||
|
||||
if (false === $shouldRetry) {
|
||||
yield from $this->passthru($context, $firstChunk, $content, $chunk);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} elseif ($chunk->isFirst()) {
|
||||
if (false === $shouldRetry = $this->strategy->shouldRetry($context, null, null)) {
|
||||
yield from $this->passthru($context, $firstChunk, $content, $chunk);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Body is needed to decide
|
||||
if (null === $shouldRetry) {
|
||||
$firstChunk = $chunk;
|
||||
$content = '';
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!$chunk->isLast()) {
|
||||
$content .= $chunk->getContent();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (null === $shouldRetry = $this->strategy->shouldRetry($context, $content, null)) {
|
||||
throw new \LogicException(sprintf('The "%s::shouldRetry()" method must not return null when called with a body.', \get_class($this->strategy)));
|
||||
}
|
||||
|
||||
if (false === $shouldRetry) {
|
||||
yield from $this->passthru($context, $firstChunk, $content, $chunk);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$context->getResponse()->cancel();
|
||||
|
||||
$delay = $this->getDelayFromHeader($context->getHeaders()) ?? $this->strategy->getDelay($context, !$exception && $chunk->isLast() ? $content : null, $exception);
|
||||
++$retryCount;
|
||||
$content = '';
|
||||
$firstChunk = null;
|
||||
|
||||
$this->logger->info('Try #{count} after {delay}ms'.($exception ? ': '.$exception->getMessage() : ', status code: '.$context->getStatusCode()), [
|
||||
'count' => $retryCount,
|
||||
'delay' => $delay,
|
||||
]);
|
||||
|
||||
$context->setInfo('retry_count', $retryCount);
|
||||
$context->replaceRequest($method, $url, $options);
|
||||
$context->pause($delay / 1000);
|
||||
|
||||
if ($retryCount >= $this->maxRetries) {
|
||||
$context->passthru();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function getDelayFromHeader(array $headers): ?int
|
||||
{
|
||||
if (null !== $after = $headers['retry-after'][0] ?? null) {
|
||||
if (is_numeric($after)) {
|
||||
return (int) ($after * 1000);
|
||||
}
|
||||
|
||||
if (false !== $time = strtotime($after)) {
|
||||
return max(0, $time - time()) * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function passthru(AsyncContext $context, ?ChunkInterface $firstChunk, string &$content, ChunkInterface $lastChunk): \Generator
|
||||
{
|
||||
$context->passthru();
|
||||
|
||||
if (null !== $firstChunk) {
|
||||
yield $firstChunk;
|
||||
}
|
||||
|
||||
if ('' !== $content) {
|
||||
$chunk = $context->createChunk($content);
|
||||
$content = '';
|
||||
|
||||
yield $chunk;
|
||||
}
|
||||
|
||||
yield $lastChunk;
|
||||
}
|
||||
}
|
||||
131
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/ScopingHttpClient.php
vendored
Normal file
131
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/ScopingHttpClient.php
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Exception\InvalidArgumentException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* Auto-configure the default options based on the requested URL.
|
||||
*
|
||||
* @author Anthony Martin <anthony.martin@sensiolabs.com>
|
||||
*/
|
||||
class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface
|
||||
{
|
||||
use HttpClientTrait;
|
||||
|
||||
private $client;
|
||||
private $defaultOptionsByRegexp;
|
||||
private $defaultRegexp;
|
||||
|
||||
public function __construct(HttpClientInterface $client, array $defaultOptionsByRegexp, ?string $defaultRegexp = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->defaultOptionsByRegexp = $defaultOptionsByRegexp;
|
||||
$this->defaultRegexp = $defaultRegexp;
|
||||
|
||||
if (null !== $defaultRegexp && !isset($defaultOptionsByRegexp[$defaultRegexp])) {
|
||||
throw new InvalidArgumentException(sprintf('No options are mapped to the provided "%s" default regexp.', $defaultRegexp));
|
||||
}
|
||||
}
|
||||
|
||||
public static function forBaseUri(HttpClientInterface $client, string $baseUri, array $defaultOptions = [], ?string $regexp = null): self
|
||||
{
|
||||
if (null === $regexp) {
|
||||
$regexp = preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($baseUri))));
|
||||
}
|
||||
|
||||
$defaultOptions['base_uri'] = $baseUri;
|
||||
|
||||
return new self($client, [$regexp => $defaultOptions], $regexp);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$e = null;
|
||||
$url = self::parseUrl($url, $options['query'] ?? []);
|
||||
|
||||
if (\is_string($options['base_uri'] ?? null)) {
|
||||
$options['base_uri'] = self::parseUrl($options['base_uri']);
|
||||
}
|
||||
|
||||
try {
|
||||
$url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null));
|
||||
} catch (InvalidArgumentException $e) {
|
||||
if (null === $this->defaultRegexp) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$defaultOptions = $this->defaultOptionsByRegexp[$this->defaultRegexp];
|
||||
$options = self::mergeDefaultOptions($options, $defaultOptions, true);
|
||||
if (\is_string($options['base_uri'] ?? null)) {
|
||||
$options['base_uri'] = self::parseUrl($options['base_uri']);
|
||||
}
|
||||
$url = implode('', self::resolveUrl($url, $options['base_uri'] ?? null, $defaultOptions['query'] ?? []));
|
||||
}
|
||||
|
||||
foreach ($this->defaultOptionsByRegexp as $regexp => $defaultOptions) {
|
||||
if (preg_match("{{$regexp}}A", $url)) {
|
||||
if (null === $e || $regexp !== $this->defaultRegexp) {
|
||||
$options = self::mergeDefaultOptions($options, $defaultOptions, true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->client->request($method, $url, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
return $this->client->stream($responses, $timeout);
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
if ($this->client instanceof LoggerAwareInterface) {
|
||||
$this->client->setLogger($logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function withOptions(array $options): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->client = $this->client->withOptions($options);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
}
|
||||
120
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/TraceableHttpClient.php
vendored
Normal file
120
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-client/TraceableHttpClient.php
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpClient;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerAwareInterface;
|
||||
use KadenceWP\KadenceBlocks\Psr\Log\LoggerInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\ResponseStream;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpClient\Response\TraceableResponse;
|
||||
use Symfony\Component\Stopwatch\Stopwatch;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Contracts\Service\ResetInterface;
|
||||
|
||||
/**
|
||||
* @author Jérémy Romey <jeremy@free-agent.fr>
|
||||
*/
|
||||
final class TraceableHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface
|
||||
{
|
||||
private $client;
|
||||
private $stopwatch;
|
||||
private $tracedRequests;
|
||||
|
||||
public function __construct(HttpClientInterface $client, ?Stopwatch $stopwatch = null)
|
||||
{
|
||||
$this->client = $client;
|
||||
$this->stopwatch = $stopwatch;
|
||||
$this->tracedRequests = new \ArrayObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function request(string $method, string $url, array $options = []): ResponseInterface
|
||||
{
|
||||
$content = null;
|
||||
$traceInfo = [];
|
||||
$this->tracedRequests[] = [
|
||||
'method' => $method,
|
||||
'url' => $url,
|
||||
'options' => $options,
|
||||
'info' => &$traceInfo,
|
||||
'content' => &$content,
|
||||
];
|
||||
$onProgress = $options['on_progress'] ?? null;
|
||||
|
||||
if (false === ($options['extra']['trace_content'] ?? true)) {
|
||||
unset($content);
|
||||
$content = false;
|
||||
}
|
||||
|
||||
$options['on_progress'] = function (int $dlNow, int $dlSize, array $info) use (&$traceInfo, $onProgress) {
|
||||
$traceInfo = $info;
|
||||
|
||||
if (null !== $onProgress) {
|
||||
$onProgress($dlNow, $dlSize, $info);
|
||||
}
|
||||
};
|
||||
|
||||
return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content, null === $this->stopwatch ? null : $this->stopwatch->start("$method $url", 'http_client'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stream($responses, ?float $timeout = null): ResponseStreamInterface
|
||||
{
|
||||
if ($responses instanceof TraceableResponse) {
|
||||
$responses = [$responses];
|
||||
} elseif (!is_iterable($responses)) {
|
||||
throw new \TypeError(sprintf('"%s()" expects parameter 1 to be an iterable of TraceableResponse objects, "%s" given.', __METHOD__, get_debug_type($responses)));
|
||||
}
|
||||
|
||||
return new ResponseStream(TraceableResponse::stream($this->client, $responses, $timeout));
|
||||
}
|
||||
|
||||
public function getTracedRequests(): array
|
||||
{
|
||||
return $this->tracedRequests->getArrayCopy();
|
||||
}
|
||||
|
||||
public function reset()
|
||||
{
|
||||
if ($this->client instanceof ResetInterface) {
|
||||
$this->client->reset();
|
||||
}
|
||||
|
||||
$this->tracedRequests->exchangeArray([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): void
|
||||
{
|
||||
if ($this->client instanceof LoggerAwareInterface) {
|
||||
$this->client->setLogger($logger);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function withOptions(array $options): self
|
||||
{
|
||||
$clone = clone $this;
|
||||
$clone->client = $this->client->withOptions($options);
|
||||
|
||||
return $clone;
|
||||
}
|
||||
}
|
||||
168
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-foundation/AcceptHeader.php
vendored
Normal file
168
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-foundation/AcceptHeader.php
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(AcceptHeaderItem::class);
|
||||
|
||||
/**
|
||||
* Represents an Accept-* header.
|
||||
*
|
||||
* An accept header is compound with a list of items,
|
||||
* sorted by descending quality.
|
||||
*
|
||||
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||
*/
|
||||
class AcceptHeader
|
||||
{
|
||||
/**
|
||||
* @var AcceptHeaderItem[]
|
||||
*/
|
||||
private $items = [];
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $sorted = true;
|
||||
|
||||
/**
|
||||
* @param AcceptHeaderItem[] $items
|
||||
*/
|
||||
public function __construct(array $items)
|
||||
{
|
||||
foreach ($items as $item) {
|
||||
$this->add($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an AcceptHeader instance from a string.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromString(?string $headerValue)
|
||||
{
|
||||
$index = 0;
|
||||
|
||||
$parts = HeaderUtils::split($headerValue ?? '', ',;=');
|
||||
|
||||
return new self(array_map(function ($subParts) use (&$index) {
|
||||
$part = array_shift($subParts);
|
||||
$attributes = HeaderUtils::combine($subParts);
|
||||
|
||||
$item = new AcceptHeaderItem($part[0], $attributes);
|
||||
$item->setIndex($index++);
|
||||
|
||||
return $item;
|
||||
}, $parts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns header value's string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return implode(',', $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if header has given value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $value)
|
||||
{
|
||||
return isset($this->items[$value]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given value's item, if exists.
|
||||
*
|
||||
* @return AcceptHeaderItem|null
|
||||
*/
|
||||
public function get(string $value)
|
||||
{
|
||||
return $this->items[$value] ?? $this->items[explode('/', $value)[0].'/*'] ?? $this->items['*/*'] ?? $this->items['*'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function add(AcceptHeaderItem $item)
|
||||
{
|
||||
$this->items[$item->getValue()] = $item;
|
||||
$this->sorted = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all items.
|
||||
*
|
||||
* @return AcceptHeaderItem[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
$this->sort();
|
||||
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters items on their value using given regex.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function filter(string $pattern)
|
||||
{
|
||||
return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) {
|
||||
return preg_match($pattern, $item->getValue());
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns first item.
|
||||
*
|
||||
* @return AcceptHeaderItem|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
$this->sort();
|
||||
|
||||
return !empty($this->items) ? reset($this->items) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts items by descending quality.
|
||||
*/
|
||||
private function sort(): void
|
||||
{
|
||||
if (!$this->sorted) {
|
||||
uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) {
|
||||
$qA = $a->getQuality();
|
||||
$qB = $b->getQuality();
|
||||
|
||||
if ($qA === $qB) {
|
||||
return $a->getIndex() > $b->getIndex() ? 1 : -1;
|
||||
}
|
||||
|
||||
return $qA > $qB ? -1 : 1;
|
||||
});
|
||||
|
||||
$this->sorted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation;
|
||||
|
||||
/**
|
||||
* Represents an Accept-* header item.
|
||||
*
|
||||
* @author Jean-François Simon <contact@jfsimon.fr>
|
||||
*/
|
||||
class AcceptHeaderItem
|
||||
{
|
||||
private $value;
|
||||
private $quality = 1.0;
|
||||
private $index = 0;
|
||||
private $attributes = [];
|
||||
|
||||
public function __construct(string $value, array $attributes = [])
|
||||
{
|
||||
$this->value = $value;
|
||||
foreach ($attributes as $name => $value) {
|
||||
$this->setAttribute($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an AcceptHeaderInstance instance from a string.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromString(?string $itemValue)
|
||||
{
|
||||
$parts = HeaderUtils::split($itemValue ?? '', ';=');
|
||||
|
||||
$part = array_shift($parts);
|
||||
$attributes = HeaderUtils::combine($parts);
|
||||
|
||||
return new self($part[0], $attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns header value's string representation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
$string = $this->value.($this->quality < 1 ? ';q='.$this->quality : '');
|
||||
if (\count($this->attributes) > 0) {
|
||||
$string .= '; '.HeaderUtils::toString($this->attributes, ';');
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item value.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setValue(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item quality.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setQuality(float $quality)
|
||||
{
|
||||
$this->quality = $quality;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item quality.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getQuality()
|
||||
{
|
||||
return $this->quality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the item index.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setIndex(int $index)
|
||||
{
|
||||
$this->index = $index;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the item index.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if an attribute exists.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAttribute(string $name)
|
||||
{
|
||||
return isset($this->attributes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an attribute by its name.
|
||||
*
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAttribute(string $name, $default = null)
|
||||
{
|
||||
return $this->attributes[$name] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all attributes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAttributes()
|
||||
{
|
||||
return $this->attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attribute.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttribute(string $name, string $value)
|
||||
{
|
||||
if ('q' === $name) {
|
||||
$this->quality = (float) $value;
|
||||
} else {
|
||||
$this->attributes[$name] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation;
|
||||
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use KadenceWP\KadenceBlocks\Symfony\Component\HttpFoundation\File\File;
|
||||
|
||||
/**
|
||||
* BinaryFileResponse represents an HTTP response delivering a file.
|
||||
*
|
||||
* @author Niklas Fiekas <niklas.fiekas@tu-clausthal.de>
|
||||
* @author stealth35 <stealth35-php@live.fr>
|
||||
* @author Igor Wiedler <igor@wiedler.ch>
|
||||
* @author Jordan Alliot <jordan.alliot@gmail.com>
|
||||
* @author Sergey Linnik <linniksa@gmail.com>
|
||||
*/
|
||||
class BinaryFileResponse extends Response
|
||||
{
|
||||
protected static $trustXSendfileTypeHeader = false;
|
||||
|
||||
/**
|
||||
* @var File
|
||||
*/
|
||||
protected $file;
|
||||
protected $offset = 0;
|
||||
protected $maxlen = -1;
|
||||
protected $deleteFileAfterSend = false;
|
||||
protected $chunkSize = 16 * 1024;
|
||||
|
||||
/**
|
||||
* @param \SplFileInfo|string $file The file to stream
|
||||
* @param int $status The response status code
|
||||
* @param array $headers An array of response headers
|
||||
* @param bool $public Files are public by default
|
||||
* @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
|
||||
* @param bool $autoEtag Whether the ETag header should be automatically set
|
||||
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
|
||||
*/
|
||||
public function __construct($file, int $status = 200, array $headers = [], bool $public = true, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
|
||||
{
|
||||
parent::__construct(null, $status, $headers);
|
||||
|
||||
$this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified);
|
||||
|
||||
if ($public) {
|
||||
$this->setPublic();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SplFileInfo|string $file The file to stream
|
||||
* @param int $status The response status code
|
||||
* @param array $headers An array of response headers
|
||||
* @param bool $public Files are public by default
|
||||
* @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename
|
||||
* @param bool $autoEtag Whether the ETag header should be automatically set
|
||||
* @param bool $autoLastModified Whether the Last-Modified header should be automatically set
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @deprecated since Symfony 5.2, use __construct() instead.
|
||||
*/
|
||||
public static function create($file = null, int $status = 200, array $headers = [], bool $public = true, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
|
||||
{
|
||||
kadence_blocks_trigger_deprecation('symfony/http-foundation', '5.2', 'The "%s()" method is deprecated, use "new %s()" instead.', __METHOD__, static::class);
|
||||
|
||||
return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file to stream.
|
||||
*
|
||||
* @param \SplFileInfo|string $file The file to stream
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws FileException
|
||||
*/
|
||||
public function setFile($file, ?string $contentDisposition = null, bool $autoEtag = false, bool $autoLastModified = true)
|
||||
{
|
||||
if (!$file instanceof File) {
|
||||
if ($file instanceof \SplFileInfo) {
|
||||
$file = new File($file->getPathname());
|
||||
} else {
|
||||
$file = new File((string) $file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$file->isReadable()) {
|
||||
throw new FileException('File must be readable.');
|
||||
}
|
||||
|
||||
$this->file = $file;
|
||||
|
||||
if ($autoEtag) {
|
||||
$this->setAutoEtag();
|
||||
}
|
||||
|
||||
if ($autoLastModified) {
|
||||
$this->setAutoLastModified();
|
||||
}
|
||||
|
||||
if ($contentDisposition) {
|
||||
$this->setContentDisposition($contentDisposition);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the file.
|
||||
*
|
||||
* @return File
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the response stream chunk size.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setChunkSize(int $chunkSize): self
|
||||
{
|
||||
if ($chunkSize < 1 || $chunkSize > \PHP_INT_MAX) {
|
||||
throw new \LogicException('The chunk size of a BinaryFileResponse cannot be less than 1 or greater than PHP_INT_MAX.');
|
||||
}
|
||||
|
||||
$this->chunkSize = $chunkSize;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically sets the Last-Modified header according the file modification date.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoLastModified()
|
||||
{
|
||||
$this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically sets the ETag header according to the checksum of the file.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAutoEtag()
|
||||
{
|
||||
$this->setEtag(base64_encode(hash_file('sha256', $this->file->getPathname(), true)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Content-Disposition header with the given filename.
|
||||
*
|
||||
* @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT
|
||||
* @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file
|
||||
* @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setContentDisposition(string $disposition, string $filename = '', string $filenameFallback = '')
|
||||
{
|
||||
if ('' === $filename) {
|
||||
$filename = $this->file->getFilename();
|
||||
}
|
||||
|
||||
if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($filename, '%'))) {
|
||||
$encoding = mb_detect_encoding($filename, null, true) ?: '8bit';
|
||||
|
||||
for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) {
|
||||
$char = mb_substr($filename, $i, 1, $encoding);
|
||||
|
||||
if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) {
|
||||
$filenameFallback .= '_';
|
||||
} else {
|
||||
$filenameFallback .= $char;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback);
|
||||
$this->headers->set('Content-Disposition', $dispositionHeader);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepare(Request $request)
|
||||
{
|
||||
if ($this->isInformational() || $this->isEmpty()) {
|
||||
parent::prepare($request);
|
||||
|
||||
$this->maxlen = 0;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$this->headers->has('Content-Type')) {
|
||||
$this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream');
|
||||
}
|
||||
|
||||
parent::prepare($request);
|
||||
|
||||
$this->offset = 0;
|
||||
$this->maxlen = -1;
|
||||
|
||||
if (false === $fileSize = $this->file->getSize()) {
|
||||
return $this;
|
||||
}
|
||||
$this->headers->remove('Transfer-Encoding');
|
||||
$this->headers->set('Content-Length', $fileSize);
|
||||
|
||||
if (!$this->headers->has('Accept-Ranges')) {
|
||||
// Only accept ranges on safe HTTP methods
|
||||
$this->headers->set('Accept-Ranges', $request->isMethodSafe() ? 'bytes' : 'none');
|
||||
}
|
||||
|
||||
if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) {
|
||||
// Use X-Sendfile, do not send any content.
|
||||
$type = $request->headers->get('X-Sendfile-Type');
|
||||
$path = $this->file->getRealPath();
|
||||
// Fall back to scheme://path for stream wrapped locations.
|
||||
if (false === $path) {
|
||||
$path = $this->file->getPathname();
|
||||
}
|
||||
if ('x-accel-redirect' === strtolower($type)) {
|
||||
// Do X-Accel-Mapping substitutions.
|
||||
// @link https://github.com/rack/rack/blob/main/lib/rack/sendfile.rb
|
||||
// @link https://mattbrictson.com/blog/accelerated-rails-downloads
|
||||
if (!$request->headers->has('X-Accel-Mapping')) {
|
||||
throw new \LogicException('The "X-Accel-Mapping" header must be set when "X-Sendfile-Type" is set to "X-Accel-Redirect".');
|
||||
}
|
||||
$parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping'), ',=');
|
||||
foreach ($parts as $part) {
|
||||
[$pathPrefix, $location] = $part;
|
||||
if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
|
||||
$path = $location.substr($path, \strlen($pathPrefix));
|
||||
// Only set X-Accel-Redirect header if a valid URI can be produced
|
||||
// as nginx does not serve arbitrary file paths.
|
||||
$this->headers->set($type, $path);
|
||||
$this->maxlen = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->headers->set($type, $path);
|
||||
$this->maxlen = 0;
|
||||
}
|
||||
} elseif ($request->headers->has('Range') && $request->isMethod('GET')) {
|
||||
// Process the range headers.
|
||||
if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) {
|
||||
$range = $request->headers->get('Range');
|
||||
|
||||
if (str_starts_with($range, 'bytes=')) {
|
||||
[$start, $end] = explode('-', substr($range, 6), 2) + [1 => 0];
|
||||
|
||||
$end = ('' === $end) ? $fileSize - 1 : (int) $end;
|
||||
|
||||
if ('' === $start) {
|
||||
$start = $fileSize - $end;
|
||||
$end = $fileSize - 1;
|
||||
} else {
|
||||
$start = (int) $start;
|
||||
}
|
||||
|
||||
if ($start <= $end) {
|
||||
$end = min($end, $fileSize - 1);
|
||||
if ($start < 0 || $start > $end) {
|
||||
$this->setStatusCode(416);
|
||||
$this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize));
|
||||
} elseif ($end - $start < $fileSize - 1) {
|
||||
$this->maxlen = $end < $fileSize ? $end - $start + 1 : -1;
|
||||
$this->offset = $start;
|
||||
|
||||
$this->setStatusCode(206);
|
||||
$this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize));
|
||||
$this->headers->set('Content-Length', $end - $start + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->isMethod('HEAD')) {
|
||||
$this->maxlen = 0;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function hasValidIfRangeHeader(?string $header): bool
|
||||
{
|
||||
if ($this->getEtag() === $header) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (null === $lastModified = $this->getLastModified()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $lastModified->format('D, d M Y H:i:s').' GMT' === $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendContent()
|
||||
{
|
||||
try {
|
||||
if (!$this->isSuccessful()) {
|
||||
return parent::sendContent();
|
||||
}
|
||||
|
||||
if (0 === $this->maxlen) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$out = fopen('php://output', 'w');
|
||||
$file = fopen($this->file->getPathname(), 'r');
|
||||
|
||||
ignore_user_abort(true);
|
||||
|
||||
if (0 !== $this->offset) {
|
||||
fseek($file, $this->offset);
|
||||
}
|
||||
|
||||
$length = $this->maxlen;
|
||||
while ($length && !feof($file)) {
|
||||
$read = $length > $this->chunkSize || 0 > $length ? $this->chunkSize : $length;
|
||||
|
||||
if (false === $data = fread($file, $read)) {
|
||||
break;
|
||||
}
|
||||
while ('' !== $data) {
|
||||
$read = fwrite($out, $data);
|
||||
if (false === $read || connection_aborted()) {
|
||||
break 2;
|
||||
}
|
||||
if (0 < $length) {
|
||||
$length -= $read;
|
||||
}
|
||||
$data = substr($data, $read);
|
||||
}
|
||||
}
|
||||
|
||||
fclose($out);
|
||||
fclose($file);
|
||||
} finally {
|
||||
if ($this->deleteFileAfterSend && is_file($this->file->getPathname())) {
|
||||
unlink($this->file->getPathname());
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @throws \LogicException when the content is not null
|
||||
*/
|
||||
public function setContent(?string $content)
|
||||
{
|
||||
if (null !== $content) {
|
||||
throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trust X-Sendfile-Type header.
|
||||
*/
|
||||
public static function trustXSendfileTypeHeader()
|
||||
{
|
||||
self::$trustXSendfileTypeHeader = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is set to true, the file will be unlinked after the request is sent
|
||||
* Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function deleteFileAfterSend(bool $shouldDelete = true)
|
||||
{
|
||||
$this->deleteFileAfterSend = $shouldDelete;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
296
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-foundation/CHANGELOG.md
vendored
Normal file
296
wp-content/plugins/kadence-blocks/vendor/vendor-prefixed/symfony/http-foundation/CHANGELOG.md
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Deprecate passing `null` as `$requestIp` to `IpUtils::__checkIp()`, `IpUtils::__checkIp4()` or `IpUtils::__checkIp6()`, pass an empty string instead.
|
||||
* Add the `litespeed_finish_request` method to work with Litespeed
|
||||
* Deprecate `upload_progress.*` and `url_rewriter.tags` session options
|
||||
* Allow setting session options via DSN
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Add the `SessionFactory`, `NativeSessionStorageFactory`, `PhpBridgeSessionStorageFactory` and `MockFileSessionStorageFactory` classes
|
||||
* Calling `Request::getSession()` when there is no available session throws a `SessionNotFoundException`
|
||||
* Add the `RequestStack::getSession` method
|
||||
* Deprecate the `NamespacedAttributeBag` class
|
||||
* Add `ResponseFormatSame` PHPUnit constraint
|
||||
* Deprecate the `RequestStack::getMasterRequest()` method and add `getMainRequest()` as replacement
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* added support for `X-Forwarded-Prefix` header
|
||||
* added `HeaderUtils::parseQuery()`: it does the same as `parse_str()` but preserves dots in variable names
|
||||
* added `File::getContent()`
|
||||
* added ability to use comma separated ip addresses for `RequestMatcher::matchIps()`
|
||||
* added `Request::toArray()` to parse a JSON request body to an array
|
||||
* added `RateLimiter\RequestRateLimiterInterface` and `RateLimiter\AbstractRequestRateLimiter`
|
||||
* deprecated not passing a `Closure` together with `FILTER_CALLBACK` to `ParameterBag::filter()`; wrap your filter in a closure instead.
|
||||
* Deprecated the `Request::HEADER_X_FORWARDED_ALL` constant, use either `HEADER_X_FORWARDED_FOR | HEADER_X_FORWARDED_HOST | HEADER_X_FORWARDED_PORT | HEADER_X_FORWARDED_PROTO` or `HEADER_X_FORWARDED_AWS_ELB` or `HEADER_X_FORWARDED_TRAEFIK` constants instead.
|
||||
* Deprecated `BinaryFileResponse::create()`, use `__construct()` instead
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* added `Cookie::withValue`, `Cookie::withDomain`, `Cookie::withExpires`,
|
||||
`Cookie::withPath`, `Cookie::withSecure`, `Cookie::withHttpOnly`,
|
||||
`Cookie::withRaw`, `Cookie::withSameSite`
|
||||
* Deprecate `Response::create()`, `JsonResponse::create()`,
|
||||
`RedirectResponse::create()`, and `StreamedResponse::create()` methods (use
|
||||
`__construct()` instead)
|
||||
* added `Request::preferSafeContent()` and `Response::setContentSafe()` to handle "safe" HTTP preference
|
||||
according to [RFC 8674](https://tools.ietf.org/html/rfc8674)
|
||||
* made the Mime component an optional dependency
|
||||
* added `MarshallingSessionHandler`, `IdentityMarshaller`
|
||||
* made `Session` accept a callback to report when the session is being used
|
||||
* Add support for all core cache control directives
|
||||
* Added `Symfony\Component\HttpFoundation\InputBag`
|
||||
* Deprecated retrieving non-string values using `InputBag::get()`, use `InputBag::all()` if you need access to the collection of values
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* made `Cookie` auto-secure and lax by default
|
||||
* removed classes in the `MimeType` namespace, use the Symfony Mime component instead
|
||||
* removed method `UploadedFile::getClientSize()` and the related constructor argument
|
||||
* made `Request::getSession()` throw if the session has not been set before
|
||||
* removed `Response::HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL`
|
||||
* passing a null url when instantiating a `RedirectResponse` is not allowed
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* passing arguments to `Request::isMethodSafe()` is deprecated.
|
||||
* `ApacheRequest` is deprecated, use the `Request` class instead.
|
||||
* passing a third argument to `HeaderBag::get()` is deprecated, use method `all()` instead
|
||||
* [BC BREAK] `PdoSessionHandler` with MySQL changed the type of the lifetime column,
|
||||
make sure to run `ALTER TABLE sessions MODIFY sess_lifetime INTEGER UNSIGNED NOT NULL` to
|
||||
update your database.
|
||||
* `PdoSessionHandler` now precalculates the expiry timestamp in the lifetime column,
|
||||
make sure to run `CREATE INDEX EXPIRY ON sessions (sess_lifetime)` to update your database
|
||||
to speed up garbage collection of expired sessions.
|
||||
* added `SessionHandlerFactory` to create session handlers with a DSN
|
||||
* added `IpUtils::anonymize()` to help with GDPR compliance.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* added PHPUnit constraints: `RequestAttributeValueSame`, `ResponseCookieValueSame`, `ResponseHasCookie`,
|
||||
`ResponseHasHeader`, `ResponseHeaderSame`, `ResponseIsRedirected`, `ResponseIsSuccessful`, and `ResponseStatusCodeSame`
|
||||
* deprecated `MimeTypeGuesserInterface` and `ExtensionGuesserInterface` in favor of `Symfony\Component\Mime\MimeTypesInterface`.
|
||||
* deprecated `MimeType` and `MimeTypeExtensionGuesser` in favor of `Symfony\Component\Mime\MimeTypes`.
|
||||
* deprecated `FileBinaryMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileBinaryMimeTypeGuesser`.
|
||||
* deprecated `FileinfoMimeTypeGuesser` in favor of `Symfony\Component\Mime\FileinfoMimeTypeGuesser`.
|
||||
* added `UrlHelper` that allows to get an absolute URL and a relative path for a given path
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* the default value of the "$secure" and "$samesite" arguments of Cookie's constructor
|
||||
will respectively change from "false" to "null" and from "null" to "lax" in Symfony
|
||||
5.0, you should define their values explicitly or use "Cookie::create()" instead.
|
||||
* added `matchPort()` in RequestMatcher
|
||||
|
||||
4.1.3
|
||||
-----
|
||||
|
||||
* [BC BREAK] Support for the IIS-only `X_ORIGINAL_URL` and `X_REWRITE_URL`
|
||||
HTTP headers has been dropped for security reasons.
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* Query string normalization uses `parse_str()` instead of custom parsing logic.
|
||||
* Passing the file size to the constructor of the `UploadedFile` class is deprecated.
|
||||
* The `getClientSize()` method of the `UploadedFile` class is deprecated. Use `getSize()` instead.
|
||||
* added `RedisSessionHandler` to use Redis as a session storage
|
||||
* The `get()` method of the `AcceptHeader` class now takes into account the
|
||||
`*` and `*/*` default values (if they are present in the Accept HTTP header)
|
||||
when looking for items.
|
||||
* deprecated `Request::getSession()` when no session has been set. Use `Request::hasSession()` instead.
|
||||
* added `CannotWriteFileException`, `ExtensionFileException`, `FormSizeFileException`,
|
||||
`IniSizeFileException`, `NoFileException`, `NoTmpDirFileException`, `PartialFileException` to
|
||||
handle failed `UploadedFile`.
|
||||
* added `MigratingSessionHandler` for migrating between two session handlers without losing sessions
|
||||
* added `HeaderUtils`.
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()`
|
||||
methods have been removed
|
||||
* the `Request::HEADER_CLIENT_IP` constant has been removed, use
|
||||
`Request::HEADER_X_FORWARDED_FOR` instead
|
||||
* the `Request::HEADER_CLIENT_HOST` constant has been removed, use
|
||||
`Request::HEADER_X_FORWARDED_HOST` instead
|
||||
* the `Request::HEADER_CLIENT_PROTO` constant has been removed, use
|
||||
`Request::HEADER_X_FORWARDED_PROTO` instead
|
||||
* the `Request::HEADER_CLIENT_PORT` constant has been removed, use
|
||||
`Request::HEADER_X_FORWARDED_PORT` instead
|
||||
* checking for cacheable HTTP methods using the `Request::isMethodSafe()`
|
||||
method (by not passing `false` as its argument) is not supported anymore and
|
||||
throws a `\BadMethodCallException`
|
||||
* the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes have been removed
|
||||
* setting session save handlers that do not implement `\SessionHandlerInterface` in
|
||||
`NativeSessionStorage::setSaveHandler()` is not supported anymore and throws a
|
||||
`\TypeError`
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new
|
||||
`AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper
|
||||
* deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes
|
||||
* deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()`
|
||||
* deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead
|
||||
* deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* the `Request::setTrustedProxies()` method takes a new `$trustedHeaderSet` argument,
|
||||
see https://symfony.com/doc/current/deployment/proxies.html for more info,
|
||||
* deprecated the `Request::setTrustedHeaderName()` and `Request::getTrustedHeaderName()` methods,
|
||||
* added `File\Stream`, to be passed to `BinaryFileResponse` when the size of the served file is unknown,
|
||||
disabling `Range` and `Content-Length` handling, switching to chunked encoding instead
|
||||
* added the `Cookie::fromString()` method that allows to create a cookie from a
|
||||
raw header string
|
||||
|
||||
3.1.0
|
||||
-----
|
||||
|
||||
* Added support for creating `JsonResponse` with a string of JSON data
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
* The precedence of parameters returned from `Request::get()` changed from "GET, PATH, BODY" to "PATH, GET, BODY"
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* Finding deep items in `ParameterBag::get()` is deprecated since version 2.8 and
|
||||
will be removed in 3.0.
|
||||
|
||||
2.6.0
|
||||
-----
|
||||
|
||||
* PdoSessionHandler changes
|
||||
- implemented different session locking strategies to prevent loss of data by concurrent access to the same session
|
||||
- [BC BREAK] save session data in a binary column without base64_encode
|
||||
- [BC BREAK] added lifetime column to the session table which allows to have different lifetimes for each session
|
||||
- implemented lazy connections that are only opened when a session is used by either passing a dsn string
|
||||
explicitly or falling back to session.save_path ini setting
|
||||
- added a createTable method that initializes a correctly defined table depending on the database vendor
|
||||
|
||||
2.5.0
|
||||
-----
|
||||
|
||||
* added `JsonResponse::setEncodingOptions()` & `JsonResponse::getEncodingOptions()` for easier manipulation
|
||||
of the options used while encoding data to JSON format.
|
||||
|
||||
2.4.0
|
||||
-----
|
||||
|
||||
* added RequestStack
|
||||
* added Request::getEncodings()
|
||||
* added accessors methods to session handlers
|
||||
|
||||
2.3.0
|
||||
-----
|
||||
|
||||
* added support for ranges of IPs in trusted proxies
|
||||
* `UploadedFile::isValid` now returns false if the file was not uploaded via HTTP (in a non-test mode)
|
||||
* Improved error-handling of `\Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler`
|
||||
to ensure the supplied PDO handler throws Exceptions on error (as the class expects). Added related test cases
|
||||
to verify that Exceptions are properly thrown when the PDO queries fail.
|
||||
|
||||
2.2.0
|
||||
-----
|
||||
|
||||
* fixed the Request::create() precedence (URI information always take precedence now)
|
||||
* added Request::getTrustedProxies()
|
||||
* deprecated Request::isProxyTrusted()
|
||||
* [BC BREAK] JsonResponse does not turn a top level empty array to an object anymore, use an ArrayObject to enforce objects
|
||||
* added a IpUtils class to check if an IP belongs to a CIDR
|
||||
* added Request::getRealMethod() to get the "real" HTTP method (getMethod() returns the "intended" HTTP method)
|
||||
* disabled _method request parameter support by default (call Request::enableHttpMethodParameterOverride() to
|
||||
enable it, and Request::getHttpMethodParameterOverride() to check if it is supported)
|
||||
* Request::splitHttpAcceptHeader() method is deprecated and will be removed in 2.3
|
||||
* Deprecated Flashbag::count() and \Countable interface, will be removed in 2.3
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
* added Request::getSchemeAndHttpHost() and Request::getUserInfo()
|
||||
* added a fluent interface to the Response class
|
||||
* added Request::isProxyTrusted()
|
||||
* added JsonResponse
|
||||
* added a getTargetUrl method to RedirectResponse
|
||||
* added support for streamed responses
|
||||
* made Response::prepare() method the place to enforce HTTP specification
|
||||
* [BC BREAK] moved management of the locale from the Session class to the Request class
|
||||
* added a generic access to the PHP built-in filter mechanism: ParameterBag::filter()
|
||||
* made FileBinaryMimeTypeGuesser command configurable
|
||||
* added Request::getUser() and Request::getPassword()
|
||||
* added support for the PATCH method in Request
|
||||
* removed the ContentTypeMimeTypeGuesser class as it is deprecated and never used on PHP 5.3
|
||||
* added ResponseHeaderBag::makeDisposition() (implements RFC 6266)
|
||||
* made mimetype to extension conversion configurable
|
||||
* [BC BREAK] Moved all session related classes and interfaces into own namespace, as
|
||||
`Symfony\Component\HttpFoundation\Session` and renamed classes accordingly.
|
||||
Session handlers are located in the subnamespace `Symfony\Component\HttpFoundation\Session\Handler`.
|
||||
* SessionHandlers must implement `\SessionHandlerInterface` or extend from the
|
||||
`Symfony\Component\HttpFoundation\Storage\Handler\NativeSessionHandler` base class.
|
||||
* Added internal storage driver proxy mechanism for forward compatibility with
|
||||
PHP 5.4 `\SessionHandler` class.
|
||||
* Added session handlers for custom Memcache, Memcached and Null session save handlers.
|
||||
* [BC BREAK] Removed `NativeSessionStorage` and replaced with `NativeFileSessionHandler`.
|
||||
* [BC BREAK] `SessionStorageInterface` methods removed: `write()`, `read()` and
|
||||
`remove()`. Added `getBag()`, `registerBag()`. The `NativeSessionStorage` class
|
||||
is a mediator for the session storage internals including the session handlers
|
||||
which do the real work of participating in the internal PHP session workflow.
|
||||
* [BC BREAK] Introduced mock implementations of `SessionStorage` to enable unit
|
||||
and functional testing without starting real PHP sessions. Removed
|
||||
`ArraySessionStorage`, and replaced with `MockArraySessionStorage` for unit
|
||||
tests; removed `FilesystemSessionStorage`, and replaced with`MockFileSessionStorage`
|
||||
for functional tests. These do not interact with global session ini
|
||||
configuration values, session functions or `$_SESSION` superglobal. This means
|
||||
they can be configured directly allowing multiple instances to work without
|
||||
conflicting in the same PHP process.
|
||||
* [BC BREAK] Removed the `close()` method from the `Session` class, as this is
|
||||
now redundant.
|
||||
* Deprecated the following methods from the Session class: `setFlash()`, `setFlashes()`
|
||||
`getFlash()`, `hasFlash()`, and `removeFlash()`. Use `getFlashBag()` instead
|
||||
which returns a `FlashBagInterface`.
|
||||
* `Session->clear()` now only clears session attributes as before it cleared
|
||||
flash messages and attributes. `Session->getFlashBag()->all()` clears flashes now.
|
||||
* Session data is now managed by `SessionBagInterface` to better encapsulate
|
||||
session data.
|
||||
* Refactored session attribute and flash messages system to their own
|
||||
`SessionBagInterface` implementations.
|
||||
* Added `FlashBag`. Flashes expire when retrieved by `get()` or `all()`. This
|
||||
implementation is ESI compatible.
|
||||
* Added `AutoExpireFlashBag` (default) to replicate Symfony 2.0.x auto expire
|
||||
behavior of messages auto expiring after one page page load. Messages must
|
||||
be retrieved by `get()` or `all()`.
|
||||
* Added `Symfony\Component\HttpFoundation\Attribute\AttributeBag` to replicate
|
||||
attributes storage behavior from 2.0.x (default).
|
||||
* Added `Symfony\Component\HttpFoundation\Attribute\NamespacedAttributeBag` for
|
||||
namespace session attributes.
|
||||
* Flash API can stores messages in an array so there may be multiple messages
|
||||
per flash type. The old `Session` class API remains without BC break as it
|
||||
will allow single messages as before.
|
||||
* Added basic session meta-data to the session to record session create time,
|
||||
last updated time, and the lifetime of the session cookie that was provided
|
||||
to the client.
|
||||
* Request::getClientIp() method doesn't take a parameter anymore but bases
|
||||
itself on the trustProxy parameter.
|
||||
* Added isMethod() to Request object.
|
||||
* [BC BREAK] The methods `getPathInfo()`, `getBaseUrl()` and `getBasePath()` of
|
||||
a `Request` now all return a raw value (vs a urldecoded value before). Any call
|
||||
to one of these methods must be checked and wrapped in a `rawurldecode()` if
|
||||
needed.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user