Use Certainty library for SSL CA bundles, implement DI-injected HTTP Client

This commit is contained in:
Buster "Silver Eagle" Neece 2018-09-06 20:08:47 -05:00
parent 4d461049ad
commit 2e631c9502
20 changed files with 375 additions and 124 deletions

View File

@ -42,7 +42,8 @@
"supervisorphp/supervisor": "^3.0",
"symfony/finder": "^4.1",
"zendframework/zend-config": "^3.1.0",
"zendframework/zend-paginator": "^2.7"
"zendframework/zend-paginator": "^2.7",
"paragonie/certainty": "^2"
},
"require-dev": {
"codeception/codeception": "^2.2",

250
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "43f5da23019a8af219184bd2c1af7a3a",
"content-hash": "8298a58760760657a50f65006b54c631",
"packages": [
{
"name": "azuracast/azuraforms",
@ -2330,6 +2330,254 @@
"description": "Generic PHP Helper Classes",
"time": "2018-09-04T09:50:54+00:00"
},
{
"name": "paragonie/certainty",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/certainty.git",
"reference": "30364451a3615d4b3813bb0857b85b10815b4487"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/certainty/zipball/30364451a3615d4b3813bb0857b85b10815b4487",
"reference": "30364451a3615d4b3813bb0857b85b10815b4487",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6",
"paragonie/constant_time_encoding": "^1|^2",
"paragonie/sodium_compat": "^1.6",
"php": "^5.5|^7"
},
"require-dev": {
"phpunit/phpunit": "^4|^5|^6"
},
"bin": [
"bin/certainty-cert-symlink"
],
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\Certainty\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"ISC"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "Up-to-date, verifiable repository for Certificate Authorities",
"keywords": [
"CA-Cert",
"Ed25519",
"Public-Key Infractructure",
"ca",
"ca-cert.pem",
"cacert",
"cacert.pem",
"certificate authority",
"pki",
"ssl",
"tls"
],
"time": "2018-05-03T17:31:41+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.2.2",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "eccf915f45f911bfb189d1d1638d940ec6ee6e33"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/eccf915f45f911bfb189d1d1638d940ec6ee6e33",
"reference": "eccf915f45f911bfb189d1d1638d940ec6ee6e33",
"shasum": ""
},
"require": {
"php": "^7"
},
"require-dev": {
"phpunit/phpunit": "^6|^7",
"vimeo/psalm": "^1"
},
"type": "library",
"autoload": {
"psr-4": {
"ParagonIE\\ConstantTime\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com",
"role": "Maintainer"
},
{
"name": "Steve 'Sc00bz' Thomas",
"email": "steve@tobtu.com",
"homepage": "https://www.tobtu.com",
"role": "Original Developer"
}
],
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
"keywords": [
"base16",
"base32",
"base32_decode",
"base32_encode",
"base64",
"base64_decode",
"base64_encode",
"bin2hex",
"encoding",
"hex",
"hex2bin",
"rfc4648"
],
"time": "2018-03-10T19:47:49+00:00"
},
{
"name": "paragonie/random_compat",
"version": "v9.99.99",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"shasum": ""
},
"require": {
"php": "^7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com",
"homepage": "https://paragonie.com"
}
],
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"time": "2018-07-02T15:55:56+00:00"
},
{
"name": "paragonie/sodium_compat",
"version": "v1.6.4",
"source": {
"type": "git",
"url": "https://github.com/paragonie/sodium_compat.git",
"reference": "3f2fd07977541b4d630ea0365ad0eceddee5179c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/3f2fd07977541b4d630ea0365ad0eceddee5179c",
"reference": "3f2fd07977541b4d630ea0365ad0eceddee5179c",
"shasum": ""
},
"require": {
"paragonie/random_compat": ">=1",
"php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7"
},
"require-dev": {
"phpunit/phpunit": "^3|^4|^5"
},
"suggest": {
"ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
"ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
},
"type": "library",
"autoload": {
"files": [
"autoload.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"ISC"
],
"authors": [
{
"name": "Paragon Initiative Enterprises",
"email": "security@paragonie.com"
},
{
"name": "Frank Denis",
"email": "jedisct1@pureftpd.org"
}
],
"description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
"keywords": [
"Authentication",
"BLAKE2b",
"ChaCha20",
"ChaCha20-Poly1305",
"Chapoly",
"Curve25519",
"Ed25519",
"EdDSA",
"Edwards-curve Digital Signature Algorithm",
"Elliptic Curve Diffie-Hellman",
"Poly1305",
"Pure-PHP cryptography",
"RFC 7748",
"RFC 8032",
"Salpoly",
"Salsa20",
"X25519",
"XChaCha20-Poly1305",
"XSalsa20-Poly1305",
"Xchacha20",
"Xsalsa20",
"aead",
"cryptography",
"ecdh",
"elliptic curve",
"elliptic curve cryptography",
"encryption",
"libsodium",
"php",
"public-key cryptography",
"secret-key cryptography",
"side-channel resistant"
],
"time": "2018-08-29T22:02:48+00:00"
},
{
"name": "php-http/discovery",
"version": "1.4.0",

View File

@ -229,7 +229,7 @@ return function (\Slim\Container $di, $settings) {
return $supervisor;
};
$di[App\View::class] = $di->factory(function(\Slim\Container $di) {
$di[\App\View::class] = $di->factory(function(\Slim\Container $di) {
$view = new App\View(dirname(__DIR__) . '/resources/templates');
$view->setFileExtension('phtml');
@ -308,6 +308,17 @@ return function (\Slim\Container $di, $settings) {
return new \MaxMind\Db\Reader($mmdb_path);
};
$di[\GuzzleHttp\Client::class] = function($di) {
$fetcher = new \ParagonIE\Certainty\RemoteFetch(APP_INCLUDE_TEMP);
$latestCACertBundle = $fetcher->getLatestBundle();
return new \GuzzleHttp\Client([
'verify' => $latestCACertBundle->getFilePath(),
'http_errors' => false,
'timeout' => 3.0,
]);
};
//
// AzuraCast-specific dependencies
//

View File

@ -60,6 +60,7 @@ class RadioProvider implements ServiceProviderInterface
$di[\Doctrine\ORM\EntityManager::class],
$di[\Supervisor\Supervisor::class],
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class],
$di['router']
);
});
@ -69,6 +70,7 @@ class RadioProvider implements ServiceProviderInterface
$di[\Doctrine\ORM\EntityManager::class],
$di[\Supervisor\Supervisor::class],
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class],
$di['router']
);
});
@ -78,6 +80,7 @@ class RadioProvider implements ServiceProviderInterface
$di[\Doctrine\ORM\EntityManager::class],
$di[\Supervisor\Supervisor::class],
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class],
$di['router']
);
});

View File

@ -29,19 +29,22 @@ class WebhookProvider implements ServiceProviderInterface
$di[Webhook\Connector\Discord::class] = function($di) {
return new Webhook\Connector\Discord(
$di[\Monolog\Logger::class]
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class]
);
};
$di[Webhook\Connector\Generic::class] = function($di) {
return new Webhook\Connector\Generic(
$di[\Monolog\Logger::class]
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class]
);
};
$di[Webhook\Connector\Local::class] = function($di) {
return new Webhook\Connector\Local(
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class],
$di[\InfluxDB\Database::class],
$di[\App\Cache::class],
$di[\App\Entity\Repository\SettingsRepository::class]
@ -50,19 +53,22 @@ class WebhookProvider implements ServiceProviderInterface
$di[Webhook\Connector\TuneIn::class] = function($di) {
return new Webhook\Connector\TuneIn(
$di[\Monolog\Logger::class]
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class]
);
};
$di[Webhook\Connector\Telegram::class] = function($di) {
return new Webhook\Connector\Telegram(
$di[\Monolog\Logger::class]
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class]
);
};
$di[Webhook\Connector\Twitter::class] = function($di) {
return new Webhook\Connector\Twitter(
$di[\Monolog\Logger::class]
$di[\Monolog\Logger::class],
$di[\GuzzleHttp\Client::class]
);
};
}

View File

@ -38,7 +38,7 @@ abstract class AdapterAbstract
/**
* @param Station $station
*/
public function setStation(Station $station)
public function setStation(Station $station): void
{
$this->station = $station;
}
@ -47,19 +47,19 @@ abstract class AdapterAbstract
* Read configuration from external service to Station object.
* @return bool
*/
abstract public function read();
abstract public function read(): bool;
/**
* Write configuration from Station object to the external service.
* @return bool
*/
abstract public function write();
abstract public function write(): bool;
/**
* Return the shell command required to run the program.
* @return string|null
*/
public function getCommand()
public function getCommand(): ?string
{
return null;
}
@ -68,7 +68,7 @@ abstract class AdapterAbstract
* Return a boolean indicating whether the adapter has an executable command associated with it.
* @return bool
*/
public function hasCommand()
public function hasCommand(): bool
{
if (APP_TESTING_MODE || !$this->station->isEnabled()) {
return false;
@ -82,7 +82,7 @@ abstract class AdapterAbstract
*
* @return bool
*/
public function isRunning()
public function isRunning(): bool
{
if ($this->hasCommand()) {
$program_name = $this->getProgramName();
@ -102,7 +102,7 @@ abstract class AdapterAbstract
* @throws \App\Exception\Supervisor
* @throws \App\Exception\Supervisor\NotRunning
*/
public function stop()
public function stop(): void
{
if ($this->hasCommand()) {
$program_name = $this->getProgramName();
@ -122,7 +122,7 @@ abstract class AdapterAbstract
* @throws \App\Exception\Supervisor
* @throws \App\Exception\Supervisor\AlreadyRunning
*/
public function start()
public function start(): void
{
if ($this->hasCommand()) {
$program_name = $this->getProgramName();
@ -143,7 +143,7 @@ abstract class AdapterAbstract
* @throws \App\Exception\Supervisor\AlreadyRunning
* @throws \App\Exception\Supervisor\NotRunning
*/
public function restart()
public function restart(): void
{
$this->stop();
$this->start();
@ -151,25 +151,22 @@ abstract class AdapterAbstract
/**
* Return the program's fully qualified supervisord name.
*
* @return bool
* @return string
*/
abstract public function getProgramName();
abstract public function getProgramName(): string;
/**
* Indicate if the adapter in question is installed on the server.
*
* @return bool
*/
public static function isInstalled()
public static function isInstalled(): bool
{
return (static::getBinary() !== false);
}
/**
* Return the binary executable location for this item.
*
* @return bool
*/
public static function getBinary()
{
@ -185,17 +182,17 @@ abstract class AdapterAbstract
* @throws \App\Exception\Supervisor\AlreadyRunning
* @throws \App\Exception\Supervisor\NotRunning
*/
protected function _handleSupervisorException(FaultException $e, $program_name)
protected function _handleSupervisorException(FaultException $e, $program_name): void
{
if (false !== stripos($e->getMessage(), 'ALREADY_STARTED')) {
$app_e = new \App\Exception\Supervisor\AlreadyRunning(
sprintf('Adapter "%s" cannot start; was already running.', get_called_class()),
sprintf('Adapter "%s" cannot start; was already running.', static::class),
$e->getCode(),
$e
);
} else if (false !== stripos($e->getMessage(), 'NOT_RUNNING')) {
$app_e = new \App\Exception\Supervisor\NotRunning(
sprintf('Adapter "%s" cannot start; was already running.', get_called_class()),
sprintf('Adapter "%s" cannot start; was already running.', static::class),
$e->getCode(),
$e
);

View File

@ -29,7 +29,7 @@ abstract class BackendAbstract extends \App\Radio\AdapterAbstract
return null;
}
public function getProgramName()
public function getProgramName(): string
{
return 'station_' . $this->station->getId() . ':station_' . $this->station->getId() . '_backend';
}

View File

@ -29,7 +29,7 @@ class Liquidsoap extends BackendAbstract
/**
* @inheritdoc
*/
public function read()
public function read(): bool
{
// This function not implemented for LiquidSoap.
}
@ -40,10 +40,10 @@ class Liquidsoap extends BackendAbstract
* Special thanks to the team of PonyvilleFM for assisting with Liquidsoap configuration and debugging.
*
* @return bool
* @throws \Doctrine\ORM\OptimisticLockException
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function write()
public function write(): bool
{
$settings = (array)$this->station->getBackendConfig();
@ -487,7 +487,7 @@ class Liquidsoap extends BackendAbstract
$hours += $offset_hours;
}
$hours = $hours % 24;
$hours %= 24;
if ($hours < 0) {
$hours += 24;
}
@ -585,7 +585,7 @@ class Liquidsoap extends BackendAbstract
/**
* @inheritdoc
*/
public function getCommand()
public function getCommand(): ?string
{
if ($binary = self::getBinary()) {
$config_path = $this->station->getRadioConfigDir() . '/liquidsoap.liq';
@ -698,7 +698,7 @@ class Liquidsoap extends BackendAbstract
*/
public static function getBinary()
{
$user_base = dirname(APP_INCLUDE_ROOT);
$user_base = \dirname(APP_INCLUDE_ROOT);
$new_path = $user_base . '/.opam/system/bin/liquidsoap';
$legacy_path = '/usr/bin/liquidsoap';
@ -716,7 +716,7 @@ class Liquidsoap extends BackendAbstract
* INTERNAL LIQUIDSOAP COMMANDS
*/
public function authenticateStreamer($user, $pass)
public function authenticateStreamer($user, $pass): string
{
// Allow connections using the exact broadcast source password.
$fe_config = (array)$this->station->getFrontendConfig();
@ -726,10 +726,10 @@ class Liquidsoap extends BackendAbstract
// Handle login conditions where the username and password are joined in the password field.
if (strpos($pass, ',') !== false) {
list($user, $pass) = explode(',', $pass);
[$user, $pass] = explode(',', $pass);
}
if (strpos($pass, ':') !== false) {
list($user, $pass) = explode(':', $pass);
[$user, $pass] = explode(':', $pass);
}
/** @var Entity\Repository\StationStreamerRepository $streamer_repo */
@ -749,7 +749,7 @@ class Liquidsoap extends BackendAbstract
return 'false';
}
public function getNextSong($as_autodj = false)
public function getNextSong($as_autodj = false): string
{
/** @var Entity\SongHistory|null $sh */
$sh = $this->autodj->getNextSong($this->station, $as_autodj);
@ -762,12 +762,12 @@ class Liquidsoap extends BackendAbstract
}
}
return (APP_INSIDE_DOCKER)
? '/usr/local/share/icecast/web/error.mp3' :
APP_INCLUDE_ROOT . '/resources/error.mp3';
return APP_INSIDE_DOCKER
? '/usr/local/share/icecast/web/error.mp3'
: APP_INCLUDE_ROOT . '/resources/error.mp3';
}
public function toggleLiveStatus($is_streamer_live = true)
public function toggleLiveStatus($is_streamer_live = true): void
{
$this->station->setIsStreamerLive($is_streamer_live);

View File

@ -9,20 +9,20 @@ class None extends BackendAbstract
protected $supports_streamers = false;
public function read()
public function read(): bool
{
}
public function write()
public function write(): bool
{
}
public function isRunning()
public function isRunning(): bool
{
return true;
}
public function start()
public function start(): void
{
$this->logger->error('Cannot start process; AutoDJ is currently disabled.', ['station_id' => $this->station->getId(), 'station_name' => $this->station->getName()]);
}

View File

@ -5,6 +5,7 @@ use App\Http\Router;
use App\Entity;
use Doctrine\ORM\EntityManager;
use fXmlRpc\Exception\FaultException;
use GuzzleHttp\Client;
use Monolog\Logger;
use Supervisor\Supervisor;
@ -13,10 +14,14 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
/** @var Router */
protected $router;
public function __construct(EntityManager $em, Supervisor $supervisor, Logger $logger, Router $router)
/** @var Client */
protected $http_client;
public function __construct(EntityManager $em, Supervisor $supervisor, Logger $logger, Client $client, Router $router)
{
parent::__construct($em, $supervisor, $logger);
$this->http_client = $client;
$this->router = $router;
}
@ -29,12 +34,12 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
/** @var bool If set to true, all URLs for this adapter type will be proxied if the user is viewing on a secure page. */
protected $force_proxy_on_secure_pages = false;
public function supportsMounts()
public function supportsMounts(): bool
{
return $this->supports_mounts;
}
public function supportsListenerDetail()
public function supportsListenerDetail(): bool
{
return $this->supports_listener_detail;
}
@ -42,7 +47,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
/**
* @return null|string The command to pass the station-watcher app.
*/
public function getWatchCommand()
public function getWatchCommand(): ?string
{
return null;
}
@ -50,7 +55,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
/**
* @return bool Whether a station-watcher command exists for this adapter.
*/
public function hasWatchCommand()
public function hasWatchCommand(): bool
{
if (APP_TESTING_MODE || !APP_INSIDE_DOCKER || !$this->station->isEnabled()) {
return false;
@ -64,7 +69,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
*
* @return string
*/
public function getWatchProgramName()
public function getWatchProgramName(): string
{
return 'station_' . $this->station->getId() . ':station_' . $this->station->getId() . '_watcher';
}
@ -76,7 +81,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
* @param $watch_uri
* @return string
*/
protected function _getStationWatcherCommand($adapter, $watch_uri)
protected function _getStationWatcherCommand($adapter, $watch_uri): string
{
$base_url = (APP_INSIDE_DOCKER) ? 'http://nginx' : 'http://localhost';
$notify_uri = $base_url.'/api/internal/'.$this->station->getId().'/notify?api_auth='.$this->station->getAdapterApiKey();
@ -89,7 +94,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
*
* @return array
*/
public function getDefaultMounts()
public function getDefaultMounts(): array
{
return [
[
@ -102,12 +107,12 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
];
}
public function getProgramName()
public function getProgramName(): string
{
return 'station_' . $this->station->getId() . ':station_' . $this->station->getId() . '_frontend';
}
public function getStreamUrl()
public function getStreamUrl(): string
{
$mount_repo = $this->em->getRepository(Entity\StationMount::class);
$default_mount = $mount_repo->getDefaultMount($this->station);
@ -115,7 +120,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
return $this->getUrlForMount($default_mount);
}
public function getStreamUrls()
public function getStreamUrls(): array
{
$urls = [];
foreach ($this->station->getMounts() as $mount) {
@ -134,9 +139,9 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
) . '?' . time();
}
abstract public function getAdminUrl();
abstract public function getAdminUrl(): string;
public function getPublicUrl()
public function getPublicUrl(): string
{
$fe_config = (array)$this->station->getFrontendConfig();
$radio_port = $fe_config['port'];
@ -159,7 +164,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
}
/* Fetch a remote URL. */
protected function getUrl($url, $c_opts = null)
protected function getUrl($url, $c_opts = null): string
{
if (APP_TESTING_MODE) {
return '';
@ -172,14 +177,12 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
],
];
if (is_array($c_opts)) {
if (\is_array($c_opts)) {
$defaults = array_merge($defaults, $c_opts);
}
$client = new \GuzzleHttp\Client();
try {
$response = $client->get($url, $defaults);
$response = $this->http_client->get($url, $defaults);
} catch(\GuzzleHttp\Exception\ClientException $e) {
$app_e = new \App\Exception($e->getMessage(), $e->getCode(), $e);
$app_e->addLoggingContext('url', $url);
@ -196,7 +199,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
* @param bool $include_clients Whether to try to retrieve detailed listener client info
* @return array
*/
public function getNowPlaying($payload = null, $include_clients = true)
public function getNowPlaying($payload = null, $include_clients = true): array
{
// Now Playing defaults.
$np = [
@ -251,7 +254,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
*/
abstract protected function _getNowPlaying(&$np, $payload = null, $include_clients = true);
protected function _cleanUpString(&$value)
protected function _cleanUpString(&$value): void
{
$value = htmlspecialchars_decode($value);
$value = trim($value);
@ -263,13 +266,13 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
$unique_listeners = (int)$unique_listeners;
$current_listeners = (int)$current_listeners;
return ($unique_listeners == 0 || $current_listeners == 0)
return ($unique_listeners === 0 || $current_listeners === 0)
? max($unique_listeners, $current_listeners)
: min($unique_listeners, $current_listeners);
}
/* Return the artist and title from a string in the format "Artist - Title" */
protected function getSongFromString($song_string, $delimiter = '-')
protected function getSongFromString($song_string, $delimiter = '-'): array
{
// Fix ShoutCast 2 bug where 3 spaces = " - "
$song_string = str_replace(' ', ' - ', $song_string);
@ -280,7 +283,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
$string_parts = explode($delimiter, $song_string);
// If not normally delimited, return "text" only.
if (count($string_parts) == 1) {
if (count($string_parts) === 1) {
return ['text' => $song_string, 'artist' => '', 'title' => $song_string];
}
@ -298,7 +301,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
/**
* @inheritdoc
*/
public function stop()
public function stop(): void
{
parent::stop();
@ -317,7 +320,7 @@ abstract class FrontendAbstract extends \App\Radio\AdapterAbstract
/**
* @inheritdoc
*/
public function start()
public function start(): void
{
parent::start();

View File

@ -14,7 +14,7 @@ class Icecast extends FrontendAbstract
protected $force_proxy_on_secure_pages = true;
public function getWatchCommand()
public function getWatchCommand(): ?string
{
$fe_config = (array)$this->station->getFrontendConfig();
@ -152,14 +152,14 @@ class Icecast extends FrontendAbstract
return true;
}
public function read()
public function read(): bool
{
$config = $this->_getConfig();
$this->station->setFrontendConfigDefaults($this->_loadFromConfig($config));
return true;
}
public function write()
public function write(): bool
{
$config = $this->_getDefaults();
@ -218,7 +218,7 @@ class Icecast extends FrontendAbstract
* Process Management
*/
public function getCommand()
public function getCommand(): ?string
{
if ($binary = self::getBinary()) {
$config_path = $this->station->getRadioConfigDir() . '/icecast.xml';
@ -227,7 +227,7 @@ class Icecast extends FrontendAbstract
return '/bin/false';
}
public function getAdminUrl()
public function getAdminUrl(): string
{
return $this->getPublicUrl() . '/admin/';
}

View File

@ -33,22 +33,24 @@ class Remote extends FrontendAbstract
return false;
}
public function read() {}
public function read(): bool
{}
public function write() {}
public function write(): bool
{}
public function isRunning()
public function isRunning(): bool
{
return true;
}
public function getStreamUrl()
public function getStreamUrl(): string
{
$default_mount = $this->_getDefaultMount();
return $this->getUrlForMount($default_mount);
}
public function getStreamUrls()
public function getStreamUrls(): array
{
$mounts = $this->_getMounts();
@ -60,7 +62,7 @@ class Remote extends FrontendAbstract
return $stream_urls;
}
public function getPublicUrl()
public function getPublicUrl(): string
{
return $this->_getMountPublicUrl($this->_getDefaultMount());
}
@ -71,7 +73,7 @@ class Remote extends FrontendAbstract
return $np['listenurl'] ?? '';
}
public function getAdminUrl()
public function getAdminUrl(): string
{
$mount = $this->_getDefaultMount();
$remote_type = ($mount instanceof \Entity\StationMount) ? $mount->getRemoteType() : $mount['remote_type'];

View File

@ -9,7 +9,7 @@ class SHOUTcast extends FrontendAbstract
{
protected $force_proxy_on_secure_pages = true;
public function getWatchCommand()
public function getWatchCommand(): ?string
{
$fe_config = (array)$this->station->getFrontendConfig();
@ -106,14 +106,14 @@ class SHOUTcast extends FrontendAbstract
/**
* @inheritdoc
*/
public function read()
public function read(): bool
{
$config = $this->_getConfig();
$this->station->setFrontendConfigDefaults($this->_loadFromConfig($config));
return true;
}
public function write()
public function write(): bool
{
$config = $this->_getDefaults();
@ -179,7 +179,7 @@ class SHOUTcast extends FrontendAbstract
* Process Management
*/
public function getCommand()
public function getCommand(): ?string
{
if ($binary = self::getBinary()) {
$config_path = $this->station->getRadioConfigDir();
@ -191,7 +191,7 @@ class SHOUTcast extends FrontendAbstract
return '/bin/false';
}
public function getAdminUrl()
public function getAdminUrl(): string
{
return $this->getPublicUrl() . '/admin.cgi';
}

View File

@ -3,16 +3,21 @@ namespace App\Webhook\Connector;
use App\Entity;
use App\Utilities;
use GuzzleHttp\Client;
use Monolog\Logger;
abstract class AbstractConnector implements ConnectorInterface
{
/** @var Client */
protected $http_client;
/** @var Logger */
protected $logger;
public function __construct(Logger $logger)
public function __construct(Logger $logger, Client $http_client)
{
$this->logger = $logger;
$this->http_client = $http_client;
}
public function shouldDispatch(array $current_events, array $triggers): bool
@ -97,7 +102,7 @@ abstract class AbstractConnector implements ConnectorInterface
*/
protected function _getName(): string
{
$class_name = get_called_class();
$class_name = static::class;
return array_pop(explode("\\", $class_name));
}
}

View File

@ -122,13 +122,8 @@ class Discord extends AbstractConnector
// Dispatch webhook
$this->logger->debug('Dispatching Discord webhook...');
$client = new \GuzzleHttp\Client([
'http_errors' => false,
'timeout' => 2.0,
]);
try {
$response = $client->request('POST', $webhook_url, [
$response = $this->http_client->request('POST', $webhook_url, [
'headers' => [
'Content-Type' => 'application/json',
],

View File

@ -21,11 +21,6 @@ class Generic extends AbstractConnector
return;
}
$client = new \GuzzleHttp\Client([
'http_errors' => false,
'timeout' => 2.0,
]);
try {
$request_options = [
'headers' => [
@ -41,7 +36,7 @@ class Generic extends AbstractConnector
];
}
$response = $client->request('POST', $webhook_url, $request_options);
$response = $this->http_client->request('POST', $webhook_url, $request_options);
$this->logger->debug(
sprintf('Generic webhook returned code %d', $response->getStatusCode()),

View File

@ -3,6 +3,7 @@ namespace App\Webhook\Connector;
use App\Cache;
use App\Entity;
use GuzzleHttp\Client;
use InfluxDB\Database;
use Monolog\Logger;
@ -17,9 +18,9 @@ class Local extends AbstractConnector
/** @var Entity\Repository\SettingsRepository */
protected $settings_repo;
public function __construct(Logger $logger, Database $influx, Cache $cache, Entity\Repository\SettingsRepository $settings_repo)
public function __construct(Logger $logger, Client $http_client, Database $influx, Cache $cache, Entity\Repository\SettingsRepository $settings_repo)
{
parent::__construct($logger);
parent::__construct($logger, $http_client);
$this->influx = $influx;
$this->cache = $cache;

View File

@ -26,11 +26,6 @@ class Telegram extends AbstractConnector
return;
}
$client = new \GuzzleHttp\Client([
'http_errors' => false,
'timeout' => 4.0,
]);
$messages = $this->_replaceVariables([
'text' => $config['text'],
], $np);
@ -45,7 +40,7 @@ class Telegram extends AbstractConnector
'parse_mode' => $config['parse_mode'] ?? 'Markdown' // Markdown or HTML
];
$response = $client->request('POST', $webhook_url, [
$response = $this->http_client->request('POST', $webhook_url, [
'headers' => [
'Content-Type' => 'application/json',
],

View File

@ -31,14 +31,8 @@ class TuneIn extends AbstractConnector
$this->logger->debug('Dispatching TuneIn AIR API call...');
$client = new \GuzzleHttp\Client([
'base_uri' => 'http://air.radiotime.com',
'http_errors' => false,
'timeout' => 2.0,
]);
try {
$response = $client->get('/Playing.ashx', [
$response = $this->http_client->get('http://air.radiotime.com/Playing.ashx', [
'query' => [
'partnerId' => $config['partner_id'],
'partnerKey' => $config['partner_key'],

View File

@ -41,15 +41,10 @@ class Twitter extends AbstractConnector
// Dispatch webhook
$this->logger->debug('Posting to Twitter...');
$client = new \GuzzleHttp\Client([
'http_errors' => false,
'timeout' => 2.0,
'auth' => 'oauth',
'handler' => $stack,
]);
try {
$response = $client->request('POST', 'https://api.twitter.com/1.1/statuses/update.json', [
$response = $this->http_client->request('POST', 'https://api.twitter.com/1.1/statuses/update.json', [
'auth' => 'oauth',
'handler' => $stack,
'form_params' => [
'status' => $vars['message'],
],