Initial Commit

This commit is contained in:
David Stone
2024-11-30 18:24:12 -07:00
commit e8f7955c1c
5432 changed files with 1397750 additions and 0 deletions

84
dependencies/spatie/dns/src/Dns.php vendored Normal file
View File

@ -0,0 +1,84 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\Dns;
use WP_Ultimo\Dependencies\Spatie\Dns\Exceptions\CouldNotFetchDns;
use WP_Ultimo\Dependencies\Spatie\Dns\Exceptions\InvalidArgument;
use WP_Ultimo\Dependencies\Symfony\Component\Process\Process;
class Dns
{
protected string $domain = '';
protected string $nameserver = '';
protected array $recordTypes = ['A', 'AAAA', 'CNAME', 'NS', 'SOA', 'MX', 'SRV', 'TXT', 'DNSKEY', 'CAA', 'NAPTR'];
public static function of(string $domain, string $nameserver = '') : self
{
return new static($domain, $nameserver);
}
public function __construct(string $domain, string $nameserver = '')
{
if (empty($domain)) {
throw InvalidArgument::domainIsMissing();
}
$this->nameserver = $nameserver;
$this->domain = $this->sanitizeDomainName($domain);
}
public function useNameserver(string $nameserver)
{
$this->nameserver = $nameserver;
return $this;
}
public function getDomain() : string
{
return $this->domain;
}
public function getNameserver() : string
{
return $this->nameserver;
}
public function getRecords(...$types) : string
{
$types = $this->determineTypes($types);
$types = \count($types) ? $types : $this->recordTypes;
$dnsRecords = \array_map([$this, 'getRecordsOfType'], $types);
return \implode('', \array_filter($dnsRecords));
}
/**
* @throws InvalidArgument
*/
protected function determineTypes(array $types) : array
{
$types = \is_array($types[0] ?? null) ? $types[0] : $types;
$types = \array_map('strtoupper', $types);
if ($invalidTypes = \array_diff($types, $this->recordTypes)) {
throw InvalidArgument::filterIsNotAValidRecordType(\reset($invalidTypes), $this->recordTypes);
}
return $types;
}
protected function sanitizeDomainName(string $domain) : string
{
$domain = \str_replace(['http://', 'https://'], '', $domain);
$domain = \strtok($domain, '/');
return \strtolower($domain);
}
/**
* @throws CouldNotFetchDns
*/
protected function getRecordsOfType(string $type) : string
{
$nameserverPart = $this->getSpecificNameserverPart();
$command = \array_filter(['dig', '+nocmd', $nameserverPart, $this->domain, $type, '+multiline', '+noall', '+answer', '+noidnout']);
$process = new Process($command);
$process->run();
if (!$process->isSuccessful()) {
throw CouldNotFetchDns::digReturnedWithError(\trim($process->getErrorOutput()));
}
return $process->getOutput();
}
protected function getSpecificNameserverPart() : ?string
{
if ($this->nameserver === '') {
return null;
}
return '@' . $this->nameserver;
}
}

View File

@ -0,0 +1,11 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\Dns\Exceptions;
class CouldNotFetchDns extends \Exception
{
public static function digReturnedWithError($output)
{
return new static("Dig command failed with message: `{$output}`");
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\Dns\Exceptions;
use InvalidArgumentException;
class InvalidArgument extends InvalidArgumentException
{
public static function domainIsMissing()
{
return new static('A domain name is required');
}
public static function filterIsNotAValidRecordType($filter, array $validRecordTypes)
{
$recordTypeString = \implode(', ', $validRecordTypes);
return new static("The given filter `{$filter}` is not valid. It should be one of {$recordTypeString}");
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\Macroable;
use Closure;
use ReflectionClass;
use ReflectionMethod;
use BadMethodCallException;
trait Macroable
{
protected static $macros = [];
/**
* Register a custom macro.
*
* @param string $name
* @param object|callable $macro
*/
public static function macro(string $name, $macro)
{
static::$macros[$name] = $macro;
}
/**
* Mix another object into the class.
*
* @param object $mixin
*/
public static function mixin($mixin)
{
$methods = (new ReflectionClass($mixin))->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
foreach ($methods as $method) {
$method->setAccessible(\true);
static::macro($method->name, $method->invoke($mixin));
}
}
public static function hasMacro(string $name) : bool
{
return isset(static::$macros[$name]);
}
public static function __callStatic($method, $parameters)
{
if (!static::hasMacro($method)) {
throw new BadMethodCallException("Method {$method} does not exist.");
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
return \call_user_func_array(Closure::bind($macro, null, static::class), $parameters);
}
return \call_user_func_array($macro, $parameters);
}
public function __call($method, $parameters)
{
if (!static::hasMacro($method)) {
throw new BadMethodCallException("Method {$method} does not exist.");
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
return \call_user_func_array($macro->bindTo($this, static::class), $parameters);
}
return \call_user_func_array($macro, $parameters);
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\InvalidIpAddress;
class Downloader
{
/** @var int */
protected $port = 443;
/** @var string */
protected $ipAddress = null;
/** @var bool */
protected $usingIpAddress = \false;
/** @var int */
protected $timeout = 30;
/** @var bool */
protected $enableSni = \true;
/** @var bool */
protected $capturePeerChain = \false;
/** @var array */
protected $socketContextOptions = [];
/** @var bool */
protected $verifyPeer = \true;
/** @var bool */
protected $verifyPeerName = \true;
/** @var int */
protected $followLocation = 1;
public function usingPort(int $port)
{
$this->port = $port;
return $this;
}
public function usingSni(bool $sni)
{
$this->enableSni = $sni;
return $this;
}
public function withSocketContextOptions(array $socketContextOptions)
{
$this->socketContextOptions = $socketContextOptions;
return $this;
}
public function withFullChain(bool $fullChain)
{
$this->capturePeerChain = $fullChain;
return $this;
}
public function withVerifyPeer(bool $verifyPeer)
{
$this->verifyPeer = $verifyPeer;
return $this;
}
public function withVerifyPeerName(bool $verifyPeerName)
{
$this->verifyPeerName = $verifyPeerName;
return $this;
}
public function setTimeout(int $timeOutInSeconds)
{
$this->timeout = $timeOutInSeconds;
return $this;
}
public function setFollowLocation(int $followLocation)
{
$this->followLocation = $followLocation;
return $this;
}
public function fromIpAddress(string $ipAddress)
{
if (!\filter_var($ipAddress, \FILTER_VALIDATE_IP)) {
throw InvalidIpAddress::couldNotValidate($ipAddress);
}
$this->ipAddress = $ipAddress;
$this->usingIpAddress = \true;
return $this;
}
public function getCertificates(string $hostName) : array
{
$response = $this->fetchCertificates($hostName);
$remoteAddress = $response['remoteAddress'];
$peerCertificate = $response['options']['ssl']['peer_certificate'];
$peerCertificateChain = $response['options']['ssl']['peer_certificate_chain'] ?? [];
$fullCertificateChain = \array_merge([$peerCertificate], $peerCertificateChain);
$certificates = \array_map(function ($certificate) use($remoteAddress) {
$certificateFields = \openssl_x509_parse($certificate);
$fingerprint = \openssl_x509_fingerprint($certificate);
$fingerprintSha256 = \openssl_x509_fingerprint($certificate, 'sha256');
return new SslCertificate($certificateFields, $fingerprint, $fingerprintSha256, $remoteAddress);
}, $fullCertificateChain);
return \array_unique($certificates);
}
public function forHost(string $hostName) : SslCertificate
{
$hostName = (new Url($hostName))->getHostName();
$certificates = $this->getCertificates($hostName);
return $certificates[0] ?? \false;
}
public static function downloadCertificateFromUrl(string $url, int $timeout = 30, bool $verifyCertificate = \true) : SslCertificate
{
return (new static())->setTimeout($timeout)->withVerifyPeer($verifyCertificate)->withVerifyPeerName($verifyCertificate)->forHost($url);
}
protected function fetchCertificates(string $hostName) : array
{
$hostName = (new Url($hostName))->getHostName();
$sslOptions = ['capture_peer_cert' => \true, 'capture_peer_cert_chain' => $this->capturePeerChain, 'SNI_enabled' => $this->enableSni, 'peer_name' => $hostName, 'verify_peer' => $this->verifyPeer, 'verify_peer_name' => $this->verifyPeerName, 'follow_location' => $this->followLocation];
$streamContext = \stream_context_create(['socket' => $this->socketContextOptions, 'ssl' => $sslOptions]);
$connectTo = $this->usingIpAddress ? $this->ipAddress : $hostName;
$client = @\stream_socket_client("ssl://{$connectTo}:{$this->port}", $errorNumber, $errorDescription, $this->timeout, \STREAM_CLIENT_CONNECT, $streamContext);
if (!empty($errorDescription)) {
throw $this->buildFailureException($connectTo, $errorDescription);
}
if (!$client) {
$clientErrorMessage = $this->usingIpAddress ? "Could not connect to `{$connectTo}` or it does not have a certificate matching `{$hostName}`." : "Could not connect to `{$connectTo}`.";
throw CouldNotDownloadCertificate::unknownError($hostName, $clientErrorMessage);
}
$response = \stream_context_get_params($client);
$response['remoteAddress'] = \stream_socket_get_name($client, \true);
\fclose($client);
return $response;
}
protected function buildFailureException(string $hostName, string $errorDescription)
{
if (\str_contains($errorDescription, 'getaddrinfo failed')) {
return CouldNotDownloadCertificate::hostDoesNotExist($hostName);
}
if (\str_contains($errorDescription, 'error:14090086')) {
return CouldNotDownloadCertificate::noCertificateInstalled($hostName);
}
return CouldNotDownloadCertificate::unknownError($hostName, $errorDescription);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions;
use Exception;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate\HostDoesNotExist;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate\NoCertificateInstalled;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate\UnknownError;
class CouldNotDownloadCertificate extends Exception
{
public static function hostDoesNotExist(string $hostName) : self
{
return new HostDoesNotExist($hostName);
}
public static function noCertificateInstalled(string $hostName) : self
{
return new NoCertificateInstalled($hostName);
}
public static function unknownError(string $hostName, string $errorMessage) : self
{
return new UnknownError($hostName, $errorMessage);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate;
class HostDoesNotExist extends CouldNotDownloadCertificate
{
public function __construct(string $hostName)
{
parent::__construct("The host named `{$hostName}` does not exist.");
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate;
class NoCertificateInstalled extends CouldNotDownloadCertificate
{
public function __construct(string $hostName)
{
parent::__construct("Could not find a certificate on host named `{$hostName}`.");
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\CouldNotDownloadCertificate;
class UnknownError extends CouldNotDownloadCertificate
{
public function __construct(string $hostName, string $errorMessage)
{
parent::__construct("Could not download certificate for host `{$hostName}` because {$errorMessage}");
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions;
use Exception;
class InvalidIpAddress extends Exception
{
public static function couldNotValidate(string $ipAddress) : self
{
return new static("String `{$ipAddress}` is not a valid IP address.");
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions;
use Exception;
class InvalidUrl extends Exception
{
public static function couldNotValidate(string $url) : self
{
return new static("String `{$url}` is not a valid url.");
}
public static function couldNotDetermineHost(string $url) : self
{
return new static("Could not determine host from url `{$url}`.");
}
}

View File

@ -0,0 +1,225 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate;
use WP_Ultimo\Dependencies\Carbon\Carbon;
use WP_Ultimo\Dependencies\Spatie\Macroable\Macroable;
class SslCertificate
{
use Macroable;
/** @var array */
protected $rawCertificateFields = [];
/** @var string */
protected $fingerprint = '';
/** @var string */
private $fingerprintSha256 = '';
/** @var string */
private $remoteAddress = '';
public static function download() : Downloader
{
return new Downloader();
}
public static function createForHostName(string $url, int $timeout = 30, bool $verifyCertificate = \true) : self
{
return Downloader::downloadCertificateFromUrl($url, $timeout, $verifyCertificate);
}
public static function createFromFile(string $pathToCertificate) : self
{
return self::createFromString(\file_get_contents($pathToCertificate));
}
public static function createFromString(string $certificatePem) : self
{
$certificateFields = \openssl_x509_parse($certificatePem);
$fingerprint = \openssl_x509_fingerprint($certificatePem);
$fingerprintSha256 = \openssl_x509_fingerprint($certificatePem, 'sha256');
return new self($certificateFields, $fingerprint, $fingerprintSha256);
}
public function __construct(array $rawCertificateFields, string $fingerprint = '', string $fingerprintSha256 = '', string $remoteAddress = '')
{
$this->rawCertificateFields = $rawCertificateFields;
$this->fingerprint = $fingerprint;
$this->fingerprintSha256 = $fingerprintSha256;
$this->remoteAddress = $remoteAddress;
}
public function getRawCertificateFields() : array
{
return $this->rawCertificateFields;
}
public function getIssuer() : string
{
return $this->rawCertificateFields['issuer']['CN'] ?? '';
}
public function getDomain() : string
{
if (!\array_key_exists('CN', $this->rawCertificateFields['subject'])) {
return '';
}
if (\is_string($this->rawCertificateFields['subject']['CN'])) {
return $this->rawCertificateFields['subject']['CN'];
}
if (\is_array($this->rawCertificateFields['subject']['CN'])) {
return $this->rawCertificateFields['subject']['CN'][0];
}
return '';
}
public function getSignatureAlgorithm() : string
{
return $this->rawCertificateFields['signatureTypeSN'] ?? '';
}
public function getOrganization() : string
{
return $this->rawCertificateFields['issuer']['O'] ?? '';
}
public function getFingerprint() : string
{
return $this->fingerprint;
}
/**
* @return string
*/
public function getFingerprintSha256() : string
{
return $this->fingerprintSha256;
}
public function getAdditionalDomains() : array
{
$additionalDomains = \explode(', ', $this->rawCertificateFields['extensions']['subjectAltName'] ?? '');
return \array_map(function (string $domain) {
return \str_replace('DNS:', '', $domain);
}, $additionalDomains);
}
public function validFromDate() : Carbon
{
return Carbon::createFromTimestampUTC($this->rawCertificateFields['validFrom_time_t']);
}
public function expirationDate() : Carbon
{
return Carbon::createFromTimestampUTC($this->rawCertificateFields['validTo_time_t']);
}
public function lifespanInDays() : int
{
return $this->validFromDate()->diffInDays($this->expirationDate());
}
public function isExpired() : bool
{
return $this->expirationDate()->isPast();
}
public function isValid(string $url = null)
{
if (!Carbon::now()->between($this->validFromDate(), $this->expirationDate())) {
return \false;
}
if (!empty($url)) {
return $this->appliesToUrl($url ?? $this->getDomain());
}
return \true;
}
public function isSelfSigned() : bool
{
return $this->getIssuer() === $this->getDomain();
}
public function usesSha1Hash() : bool
{
$certificateFields = $this->getRawCertificateFields();
if ($certificateFields['signatureTypeSN'] === 'RSA-SHA1') {
return \true;
}
if ($certificateFields['signatureTypeLN'] === 'sha1WithRSAEncryption') {
return \true;
}
return \false;
}
public function isValidUntil(Carbon $carbon, string $url = null) : bool
{
if ($this->expirationDate()->lte($carbon)) {
return \false;
}
return $this->isValid($url);
}
public function daysUntilExpirationDate() : int
{
$endDate = $this->expirationDate();
$interval = Carbon::now()->diff($endDate);
return (int) $interval->format('%r%a');
}
public function getDomains() : array
{
$allDomains = $this->getAdditionalDomains();
$allDomains[] = $this->getDomain();
$uniqueDomains = \array_unique($allDomains);
return \array_values(\array_filter($uniqueDomains));
}
public function appliesToUrl(string $url) : bool
{
if (\filter_var($url, \FILTER_VALIDATE_IP)) {
$host = $url;
} else {
$host = (new Url($url))->getHostName();
}
$certificateHosts = $this->getDomains();
foreach ($certificateHosts as $certificateHost) {
$certificateHost = \str_replace('ip address:', '', \strtolower($certificateHost));
if ($host === $certificateHost) {
return \true;
}
if ($this->wildcardHostCoversHost($certificateHost, $host)) {
return \true;
}
}
return \false;
}
protected function wildcardHostCoversHost(string $wildcardHost, string $host) : bool
{
if ($host === $wildcardHost) {
return \true;
}
if (!starts_with($wildcardHost, '*')) {
return \false;
}
if (\substr_count($wildcardHost, '.') < \substr_count($host, '.')) {
return \false;
}
$wildcardHostWithoutWildcard = \substr($wildcardHost, 1);
$hostWithDottedPrefix = ".{$host}";
return ends_with($hostWithDottedPrefix, $wildcardHostWithoutWildcard);
}
public function getRawCertificateFieldsJson() : string
{
return \json_encode($this->getRawCertificateFields());
}
public function getHash() : string
{
return \md5($this->getRawCertificateFieldsJson());
}
public function getRemoteAddress() : string
{
return $this->remoteAddress;
}
public function __toString() : string
{
return $this->getRawCertificateFieldsJson();
}
public function containsDomain(string $domain) : bool
{
$certificateHosts = $this->getDomains();
foreach ($certificateHosts as $certificateHost) {
if ($certificateHost == $domain) {
return \true;
}
if (ends_with($domain, '.' . $certificateHost)) {
return \true;
}
}
return \false;
}
public function isPreCertificate() : bool
{
if (!\array_key_exists('extensions', $this->rawCertificateFields)) {
return \false;
}
if (!\array_key_exists('ct_precert_poison', $this->rawCertificateFields['extensions'])) {
return \false;
}
return \true;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate;
use WP_Ultimo\Dependencies\Spatie\SslCertificate\Exceptions\InvalidUrl;
class Url
{
/** @var string */
protected $url;
/** @var array */
protected $parsedUrl;
public function __construct(string $url)
{
if (!starts_with($url, ['http://', 'https://', 'ssl://'])) {
$url = "https://{$url}";
}
if (\function_exists('idn_to_ascii') && \strlen($url) < 61) {
$url = \idn_to_ascii($url, \false, \INTL_IDNA_VARIANT_UTS46);
}
if (!\filter_var($url, \FILTER_VALIDATE_URL)) {
throw InvalidUrl::couldNotValidate($url);
}
$this->url = $url;
$this->parsedUrl = \parse_url($url);
if (!isset($this->parsedUrl['host'])) {
throw InvalidUrl::couldNotDetermineHost($this->url);
}
}
public function getHostName() : string
{
return $this->parsedUrl['host'];
}
public function getPort() : int
{
return $this->parsedUrl['port'] ?? 443;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace WP_Ultimo\Dependencies\Spatie\SslCertificate;
function starts_with($haystack, $needles) : bool
{
foreach ((array) $needles as $needle) {
if ($needle != '' && \mb_strpos($haystack, $needle) === 0) {
return \true;
}
}
return \false;
}
/**
* Determine if a given string ends with a given substring.
*
* @param string $haystack
* @param string|array $needles
*
* @return bool
*/
function ends_with(string $haystack, $needles) : bool
{
foreach ((array) $needles as $needle) {
if ((string) $needle === \substr($haystack, -length($needle))) {
return \true;
}
}
return \false;
}
/**
* Returns the portion of string specified by the start and length parameters.
*
* @param string $string
* @param int $start
* @param int|null $length
*
* @return string
*/
function substr(string $string, int $start, int $length = null) : string
{
return \mb_substr($string, $start, $length, 'UTF-8');
}
/**
* Return the length of the given string.
*
* @param string $value
*
* @return int
*/
function length(string $value) : int
{
return \mb_strlen($value);
}
/**
* Determine if a given string contains a given substring.
*
* @param string $haystack
* @param string|array $needles
*
* @return bool
*/
function str_contains(string $haystack, $needles) : bool
{
foreach ((array) $needles as $needle) {
if ($needle != '' && \mb_strpos($haystack, $needle) !== \false) {
return \true;
}
}
return \false;
}