mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2024-11-26 11:26:31 +03:00
refactor(BridgeFactory): make methods only accept valid class names (#2897)
This moves the responsibility for getting a valid class name to the users of BridgeFactory, avoiding the repeated sanitation. Improper use can also be checked statically.
This commit is contained in:
parent
20bf2aa4fe
commit
dbf8c5b7ae
9 changed files with 102 additions and 71 deletions
|
@ -26,6 +26,13 @@ class ConnectivityAction implements ActionInterface
|
|||
{
|
||||
public $userData = [];
|
||||
|
||||
private BridgeFactory $bridgeFactory;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->bridgeFactory = new \BridgeFactory();
|
||||
}
|
||||
|
||||
public function execute()
|
||||
{
|
||||
if (!Debug::isEnabled()) {
|
||||
|
@ -39,7 +46,13 @@ class ConnectivityAction implements ActionInterface
|
|||
|
||||
$bridgeName = $this->userData['bridge'];
|
||||
|
||||
$this->reportBridgeConnectivity($bridgeName);
|
||||
$bridgeClassName = $this->bridgeFactory->sanitizeBridgeName($bridgeName);
|
||||
|
||||
if ($bridgeClassName === null) {
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
}
|
||||
|
||||
$this->reportBridgeConnectivity($bridgeClassName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,14 +65,12 @@ class ConnectivityAction implements ActionInterface
|
|||
* "successful": true/false
|
||||
* }
|
||||
*
|
||||
* @param string $bridgeName Name of the bridge to generate the report for
|
||||
* @param class-string<BridgeInterface> $bridgeClassName Name of the bridge to generate the report for
|
||||
* @return void
|
||||
*/
|
||||
private function reportBridgeConnectivity($bridgeName)
|
||||
private function reportBridgeConnectivity($bridgeClassName)
|
||||
{
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
|
||||
if (!$bridgeFactory->isWhitelisted($bridgeName)) {
|
||||
if (!$this->bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
header('Content-Type: text/html');
|
||||
returnServerError('Bridge is not whitelisted!');
|
||||
}
|
||||
|
@ -67,12 +78,12 @@ class ConnectivityAction implements ActionInterface
|
|||
header('Content-Type: text/json');
|
||||
|
||||
$retVal = [
|
||||
'bridge' => $bridgeName,
|
||||
'bridge' => $bridgeClassName,
|
||||
'successful' => false,
|
||||
'http_code' => 200,
|
||||
];
|
||||
|
||||
$bridge = $bridgeFactory->create($bridgeName);
|
||||
$bridge = $this->bridgeFactory->create($bridgeClassName);
|
||||
|
||||
if ($bridge === false) {
|
||||
echo json_encode($retVal);
|
||||
|
|
|
@ -26,12 +26,12 @@ class DetectAction implements ActionInterface
|
|||
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
|
||||
foreach ($bridgeFactory->getBridgeNames() as $bridgeName) {
|
||||
if (!$bridgeFactory->isWhitelisted($bridgeName)) {
|
||||
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
|
||||
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = $bridgeFactory->create($bridgeName);
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
|
||||
if ($bridge === false) {
|
||||
continue;
|
||||
|
@ -43,7 +43,7 @@ class DetectAction implements ActionInterface
|
|||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams['bridge'] = $bridgeName;
|
||||
$bridgeParams['bridge'] = $bridgeClassName;
|
||||
$bridgeParams['format'] = $format;
|
||||
|
||||
header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
|
||||
|
|
|
@ -28,21 +28,25 @@ class DisplayAction implements ActionInterface
|
|||
|
||||
public function execute()
|
||||
{
|
||||
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
|
||||
$bridgeClassName = isset($this->userData['bridge']) ? $bridgeFactory->sanitizeBridgeName($this->userData['bridge']) : null;
|
||||
|
||||
if ($bridgeClassName === null) {
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
}
|
||||
|
||||
$format = $this->userData['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
|
||||
// whitelist control
|
||||
if (!$bridgeFactory->isWhitelisted($bridge)) {
|
||||
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
|
||||
// Data retrieval
|
||||
$bridge = $bridgeFactory->create($bridge);
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
$bridge->loadConfiguration();
|
||||
|
||||
$noproxy = array_key_exists('_noproxy', $this->userData)
|
||||
|
|
|
@ -22,20 +22,20 @@ class ListAction implements ActionInterface
|
|||
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
|
||||
foreach ($bridgeFactory->getBridgeNames() as $bridgeName) {
|
||||
$bridge = $bridgeFactory->create($bridgeName);
|
||||
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
|
||||
if ($bridge === false) { // Broken bridge, show as inactive
|
||||
$list->bridges[$bridgeName] = [
|
||||
$list->bridges[$bridgeClassName] = [
|
||||
'status' => 'inactive'
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$status = $bridgeFactory->isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||
$status = $bridgeFactory->isWhitelisted($bridgeClassName) ? 'active' : 'inactive';
|
||||
|
||||
$list->bridges[$bridgeName] = [
|
||||
$list->bridges[$bridgeClassName] = [
|
||||
'status' => $status,
|
||||
'uri' => $bridge->getURI(),
|
||||
'donationUri' => $bridge->getDonationURI(),
|
||||
|
|
|
@ -25,16 +25,16 @@ final class BridgeCard
|
|||
/**
|
||||
* Get the form header for a bridge card
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param class-string<BridgeInterface> $bridgeClassName The bridge name
|
||||
* @param bool $isHttps If disabled, adds a warning to the form
|
||||
* @return string The form header
|
||||
*/
|
||||
private static function getFormHeader($bridgeName, $isHttps = false, $parameterName = '')
|
||||
private static function getFormHeader($bridgeClassName, $isHttps = false, $parameterName = '')
|
||||
{
|
||||
$form = <<<EOD
|
||||
<form method="GET" action="?">
|
||||
<input type="hidden" name="action" value="display" />
|
||||
<input type="hidden" name="bridge" value="{$bridgeName}" />
|
||||
<input type="hidden" name="bridge" value="{$bridgeClassName}" />
|
||||
EOD;
|
||||
|
||||
if (!empty($parameterName)) {
|
||||
|
@ -54,7 +54,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||
/**
|
||||
* Get the form body for a bridge
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param class-string<BridgeInterface> $bridgeClassName The bridge name
|
||||
* @param array $formats A list of supported formats
|
||||
* @param bool $isActive Indicates if a bridge is enabled or not
|
||||
* @param bool $isHttps Indicates if a bridge uses HTTPS or not
|
||||
|
@ -63,14 +63,14 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||
* @return string The form body
|
||||
*/
|
||||
private static function getForm(
|
||||
$bridgeName,
|
||||
$bridgeClassName,
|
||||
$formats,
|
||||
$isActive = false,
|
||||
$isHttps = false,
|
||||
$parameterName = '',
|
||||
$parameters = []
|
||||
) {
|
||||
$form = self::getFormHeader($bridgeName, $isHttps, $parameterName);
|
||||
$form = self::getFormHeader($bridgeClassName, $isHttps, $parameterName);
|
||||
|
||||
if (count($parameters) > 0) {
|
||||
$form .= '<div class="parameters">';
|
||||
|
@ -85,7 +85,7 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||
}
|
||||
|
||||
$idArg = 'arg-'
|
||||
. urlencode($bridgeName)
|
||||
. urlencode($bridgeClassName)
|
||||
. '-'
|
||||
. urlencode($parameterName)
|
||||
. '-'
|
||||
|
@ -297,16 +297,16 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||
/**
|
||||
* Gets a single bridge card
|
||||
*
|
||||
* @param string $bridgeName The bridge name
|
||||
* @param class-string<BridgeInterface> $bridgeClassName The bridge name
|
||||
* @param array $formats A list of formats
|
||||
* @param bool $isActive Indicates if the bridge is active or not
|
||||
* @return string The bridge card
|
||||
*/
|
||||
public static function displayBridgeCard($bridgeName, $formats, $isActive = true)
|
||||
public static function displayBridgeCard($bridgeClassName, $formats, $isActive = true)
|
||||
{
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
|
||||
$bridge = $bridgeFactory->create($bridgeName);
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
|
||||
if ($bridge == false) {
|
||||
return '';
|
||||
|
@ -340,20 +340,20 @@ This bridge is not fetching its content through a secure connection</div>';
|
|||
}
|
||||
|
||||
$card = <<<CARD
|
||||
<section id="bridge-{$bridgeName}" data-ref="{$name}">
|
||||
<section id="bridge-{$bridgeClassName}" data-ref="{$name}">
|
||||
<h2><a href="{$uri}">{$name}</a></h2>
|
||||
<p class="description">{$description}</p>
|
||||
<input type="checkbox" class="showmore-box" id="showmore-{$bridgeName}" />
|
||||
<label class="showmore" for="showmore-{$bridgeName}">Show more</label>
|
||||
<input type="checkbox" class="showmore-box" id="showmore-{$bridgeClassName}" />
|
||||
<label class="showmore" for="showmore-{$bridgeClassName}">Show more</label>
|
||||
CARD;
|
||||
|
||||
// If we don't have any parameter for the bridge, we print a generic form to load it.
|
||||
if (count($parameters) === 0) {
|
||||
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
|
||||
$card .= self::getForm($bridgeClassName, $formats, $isActive, $isHttps);
|
||||
|
||||
// Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters
|
||||
} elseif (count($parameters) === 1 && array_key_exists('global', $parameters)) {
|
||||
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']);
|
||||
$card .= self::getForm($bridgeClassName, $formats, $isActive, $isHttps, '', $parameters['global']);
|
||||
} else {
|
||||
foreach ($parameters as $parameterName => $parameter) {
|
||||
if (!is_numeric($parameterName) && $parameterName === 'global') {
|
||||
|
@ -368,11 +368,11 @@ CARD;
|
|||
$card .= '<h5>' . $parameterName . '</h5>' . PHP_EOL;
|
||||
}
|
||||
|
||||
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, $parameterName, $parameter);
|
||||
$card .= self::getForm($bridgeClassName, $formats, $isActive, $isHttps, $parameterName, $parameter);
|
||||
}
|
||||
}
|
||||
|
||||
$card .= '<label class="showless" for="showmore-' . $bridgeName . '">Show less</label>';
|
||||
$card .= '<label class="showless" for="showmore-' . $bridgeClassName . '">Show less</label>';
|
||||
if ($donationUri !== '' && $donationsAllowed) {
|
||||
$card .= '<p class="maintainer">' . $maintainer . ' ~ <a href="' . $donationUri . '">Donate</a></p>';
|
||||
} else {
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
final class BridgeFactory
|
||||
{
|
||||
private $folder;
|
||||
private $bridgeNames = [];
|
||||
/** @var array<class-string<BridgeInterface>> */
|
||||
private $bridgeClassNames = [];
|
||||
/** @var array<class-string<BridgeInterface>> */
|
||||
private $whitelist = [];
|
||||
|
||||
public function __construct(string $folder = PATH_LIB_BRIDGES)
|
||||
|
@ -12,8 +14,8 @@ final class BridgeFactory
|
|||
|
||||
// create names
|
||||
foreach (scandir($this->folder) as $file) {
|
||||
if (preg_match('/^([^.]+)Bridge\.php$/U', $file, $m)) {
|
||||
$this->bridgeNames[] = $m[1];
|
||||
if (preg_match('/^([^.]+Bridge)\.php$/U', $file, $m)) {
|
||||
$this->bridgeClassNames[] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,34 +28,48 @@ final class BridgeFactory
|
|||
$contents = '';
|
||||
}
|
||||
if ($contents === '*') { // Whitelist all bridges
|
||||
$this->whitelist = $this->getBridgeNames();
|
||||
$this->whitelist = $this->getBridgeClassNames();
|
||||
} else {
|
||||
foreach (explode("\n", $contents) as $bridgeName) {
|
||||
$this->whitelist[] = $this->sanitizeBridgeName($bridgeName);
|
||||
$bridgeClassName = $this->sanitizeBridgeName($bridgeName);
|
||||
if ($bridgeClassName !== null) {
|
||||
$this->whitelist[] = $bridgeClassName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<BridgeInterface> $name
|
||||
*/
|
||||
public function create(string $name): BridgeInterface
|
||||
{
|
||||
if (preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name)) {
|
||||
$className = sprintf('%sBridge', $this->sanitizeBridgeName($name));
|
||||
return new $className();
|
||||
}
|
||||
throw new \InvalidArgumentException('Bridge name invalid!');
|
||||
return new $name();
|
||||
}
|
||||
|
||||
public function getBridgeNames(): array
|
||||
/**
|
||||
* @return array<class-string<BridgeInterface>>
|
||||
*/
|
||||
public function getBridgeClassNames(): array
|
||||
{
|
||||
return $this->bridgeNames;
|
||||
return $this->bridgeClassNames;
|
||||
}
|
||||
|
||||
public function isWhitelisted($name): bool
|
||||
/**
|
||||
* @param class-string<BridgeInterface>|null $name
|
||||
*/
|
||||
public function isWhitelisted(string $name): bool
|
||||
{
|
||||
return in_array($this->sanitizeBridgeName($name), $this->whitelist);
|
||||
return in_array($name, $this->whitelist);
|
||||
}
|
||||
|
||||
private function sanitizeBridgeName($name)
|
||||
/**
|
||||
* Tries to turn a potentially human produced bridge name into a class name.
|
||||
*
|
||||
* @param mixed $name
|
||||
* @return class-string<BridgeInterface>|null
|
||||
*/
|
||||
public function sanitizeBridgeName($name): ?string
|
||||
{
|
||||
if (!is_string($name)) {
|
||||
return null;
|
||||
|
@ -64,21 +80,21 @@ final class BridgeFactory
|
|||
$name = $matches[1];
|
||||
}
|
||||
|
||||
// Trim trailing 'Bridge' if exists
|
||||
if (preg_match('/(.+)(?:Bridge)/i', $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
// Append 'Bridge' suffix if not present.
|
||||
if (!preg_match('/(Bridge)$/i', $name)) {
|
||||
$name = sprintf('%sBridge', $name);
|
||||
}
|
||||
|
||||
// Improve performance for correctly written bridge names
|
||||
if (in_array($name, $this->getBridgeNames())) {
|
||||
$index = array_search($name, $this->getBridgeNames());
|
||||
return $this->getBridgeNames()[$index];
|
||||
if (in_array($name, $this->getBridgeClassNames())) {
|
||||
$index = array_search($name, $this->getBridgeClassNames());
|
||||
return $this->getBridgeClassNames()[$index];
|
||||
}
|
||||
|
||||
// The name is valid if a corresponding bridge file is found on disk
|
||||
if (in_array(strtolower($name), array_map('strtolower', $this->getBridgeNames()))) {
|
||||
$index = array_search(strtolower($name), array_map('strtolower', $this->getBridgeNames()));
|
||||
return $this->getBridgeNames()[$index];
|
||||
if (in_array(strtolower($name), array_map('strtolower', $this->getBridgeClassNames()))) {
|
||||
$index = array_search(strtolower($name), array_map('strtolower', $this->getBridgeClassNames()));
|
||||
return $this->getBridgeClassNames()[$index];
|
||||
}
|
||||
|
||||
Debug::log('Invalid bridge name specified: "' . $name . '"!');
|
||||
|
|
|
@ -66,20 +66,20 @@ EOD;
|
|||
$inactiveBridges = '';
|
||||
|
||||
$bridgeFactory = new \BridgeFactory();
|
||||
$bridgeNames = $bridgeFactory->getBridgeNames();
|
||||
$bridgeClassNames = $bridgeFactory->getBridgeClassNames();
|
||||
|
||||
$formatFactory = new FormatFactory();
|
||||
$formats = $formatFactory->getFormatNames();
|
||||
|
||||
$totalBridges = count($bridgeNames);
|
||||
$totalBridges = count($bridgeClassNames);
|
||||
|
||||
foreach ($bridgeNames as $bridgeName) {
|
||||
if ($bridgeFactory->isWhitelisted($bridgeName)) {
|
||||
$body .= BridgeCard::displayBridgeCard($bridgeName, $formats);
|
||||
foreach ($bridgeClassNames as $bridgeClassName) {
|
||||
if ($bridgeFactory->isWhitelisted($bridgeClassName)) {
|
||||
$body .= BridgeCard::displayBridgeCard($bridgeClassName, $formats);
|
||||
$totalActiveBridges++;
|
||||
} elseif ($showInactive) {
|
||||
// inactive bridges
|
||||
$inactiveBridges .= BridgeCard::displayBridgeCard($bridgeName, $formats, false) . PHP_EOL;
|
||||
$inactiveBridges .= BridgeCard::displayBridgeCard($bridgeClassName, $formats, false) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class ActionImplementationTest extends TestCase
|
|||
|
||||
$this->setAction($path);
|
||||
|
||||
$methods = get_class_methods($this->obj);
|
||||
$methods = array_diff(get_class_methods($this->obj), ['__construct']);
|
||||
sort($methods);
|
||||
|
||||
$this->assertEquals($allowedMethods, $methods);
|
||||
|
|
|
@ -49,7 +49,7 @@ class ListActionTest extends TestCase
|
|||
$bridgeFactory = new BridgeFactory();
|
||||
|
||||
$this->assertEquals(
|
||||
count($bridgeFactory->getBridgeNames()),
|
||||
count($bridgeFactory->getBridgeClassNames()),
|
||||
count($items['bridges']),
|
||||
'Number of bridges doesn\'t match'
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue