2019-02-06 20:34:51 +03:00
|
|
|
<?php
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
/**
|
|
|
|
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
|
|
|
* Atom feeds for websites that don't have one.
|
|
|
|
*
|
|
|
|
* For the full license information, please view the UNLICENSE file distributed
|
|
|
|
* with this source code.
|
|
|
|
*
|
|
|
|
* @package Core
|
|
|
|
* @license http://unlicense.org/ UNLICENSE
|
|
|
|
* @link https://github.com/rss-bridge/rss-bridge
|
|
|
|
*/
|
|
|
|
|
2022-06-22 19:30:37 +03:00
|
|
|
class DisplayAction implements ActionInterface
|
|
|
|
{
|
2022-07-08 22:06:14 +03:00
|
|
|
public function execute(array $request)
|
2019-02-06 20:34:51 +03:00
|
|
|
{
|
2022-07-08 13:54:23 +03:00
|
|
|
$bridgeFactory = new \BridgeFactory();
|
|
|
|
|
2022-07-08 22:06:14 +03:00
|
|
|
$bridgeClassName = null;
|
|
|
|
if (isset($request['bridge'])) {
|
|
|
|
$bridgeClassName = $bridgeFactory->sanitizeBridgeName($request['bridge']);
|
|
|
|
}
|
2022-07-08 13:54:23 +03:00
|
|
|
|
|
|
|
if ($bridgeClassName === null) {
|
|
|
|
throw new \InvalidArgumentException('Bridge name invalid!');
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2022-07-08 22:06:14 +03:00
|
|
|
$format = $request['format']
|
2019-02-06 20:34:51 +03:00
|
|
|
or returnClientError('You must specify a format!');
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
// whitelist control
|
2022-07-08 13:54:23 +03:00
|
|
|
if (!$bridgeFactory->isWhitelisted($bridgeClassName)) {
|
2019-02-06 20:34:51 +03:00
|
|
|
throw new \Exception('This bridge is not whitelisted', 401);
|
|
|
|
die;
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
// Data retrieval
|
2022-07-08 13:54:23 +03:00
|
|
|
$bridge = $bridgeFactory->create($bridgeClassName);
|
2022-01-25 21:52:28 +03:00
|
|
|
$bridge->loadConfiguration();
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2022-07-08 22:06:14 +03:00
|
|
|
$noproxy = array_key_exists('_noproxy', $request)
|
|
|
|
&& filter_var($request['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2022-07-24 20:26:12 +03:00
|
|
|
if (Configuration::getConfig('proxy', 'url') && Configuration::getConfig('proxy', 'by_bridge') && $noproxy) {
|
2019-02-06 20:34:51 +03:00
|
|
|
define('NOPROXY', true);
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
// Cache timeout
|
|
|
|
$cache_timeout = -1;
|
2022-07-08 22:06:14 +03:00
|
|
|
if (array_key_exists('_cache_timeout', $request)) {
|
2019-02-06 20:34:51 +03:00
|
|
|
if (!CUSTOM_CACHE_TIMEOUT) {
|
2022-07-08 22:06:14 +03:00
|
|
|
unset($request['_cache_timeout']);
|
|
|
|
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($request);
|
2019-02-06 20:34:51 +03:00
|
|
|
header('Location: ' . $uri, true, 301);
|
2022-07-08 15:17:25 +03:00
|
|
|
exit;
|
2019-02-06 20:34:51 +03:00
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2022-07-08 22:06:14 +03:00
|
|
|
$cache_timeout = filter_var($request['_cache_timeout'], FILTER_VALIDATE_INT);
|
2019-02-06 20:34:51 +03:00
|
|
|
} else {
|
|
|
|
$cache_timeout = $bridge->getCacheTimeout();
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
// Remove parameters that don't concern bridges
|
|
|
|
$bridge_params = array_diff_key(
|
2022-07-08 22:06:14 +03:00
|
|
|
$request,
|
2019-02-06 20:34:51 +03:00
|
|
|
array_fill_keys(
|
2022-07-01 16:10:30 +03:00
|
|
|
[
|
2019-02-06 20:34:51 +03:00
|
|
|
'action',
|
|
|
|
'bridge',
|
|
|
|
'format',
|
|
|
|
'_noproxy',
|
|
|
|
'_cache_timeout',
|
|
|
|
'_error_time'
|
|
|
|
],
|
|
|
|
''
|
|
|
|
)
|
|
|
|
);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
// Remove parameters that don't concern caches
|
|
|
|
$cache_params = array_diff_key(
|
2022-07-08 22:06:14 +03:00
|
|
|
$request,
|
2019-02-06 20:34:51 +03:00
|
|
|
array_fill_keys(
|
2022-07-01 16:10:30 +03:00
|
|
|
[
|
2019-02-06 20:34:51 +03:00
|
|
|
'action',
|
|
|
|
'format',
|
|
|
|
'_noproxy',
|
|
|
|
'_cache_timeout',
|
|
|
|
'_error_time'
|
|
|
|
],
|
|
|
|
''
|
|
|
|
)
|
|
|
|
);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
// Initialize cache
|
2022-07-06 13:14:04 +03:00
|
|
|
$cacheFactory = new CacheFactory();
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2022-07-06 13:14:04 +03:00
|
|
|
$cache = $cacheFactory->create();
|
2019-04-29 21:12:43 +03:00
|
|
|
$cache->setScope('');
|
2019-02-06 20:34:51 +03:00
|
|
|
$cache->purgeCache(86400); // 24 hours
|
2019-04-29 21:12:43 +03:00
|
|
|
$cache->setKey($cache_params);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
$items = [];
|
|
|
|
$infos = [];
|
|
|
|
$mtime = $cache->getTime();
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
if (
|
|
|
|
$mtime !== false
|
|
|
|
&& (time() - $cache_timeout < $mtime)
|
|
|
|
&& !Debug::isEnabled()
|
|
|
|
) { // Load cached data
|
|
|
|
// Send "Not Modified" response if client supports it
|
|
|
|
// Implementation based on https://stackoverflow.com/a/10847262
|
|
|
|
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
|
|
|
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
if ($mtime <= $stime) { // Cached data is older or same
|
|
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
|
2022-07-08 15:17:25 +03:00
|
|
|
exit;
|
2019-02-06 20:34:51 +03:00
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
}
|
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
$cached = $cache->loadData();
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
if (isset($cached['items']) && isset($cached['extraInfos'])) {
|
|
|
|
foreach ($cached['items'] as $item) {
|
|
|
|
$items[] = new \FeedItem($item);
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
$infos = $cached['extraInfos'];
|
|
|
|
}
|
|
|
|
} else { // Collect new data
|
|
|
|
try {
|
|
|
|
$bridge->setDatas($bridge_params);
|
|
|
|
$bridge->collectData();
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
$items = $bridge->getItems();
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
// Transform "legacy" items to FeedItems if necessary.
|
|
|
|
// Remove this code when support for "legacy" items ends!
|
|
|
|
if (isset($items[0]) && is_array($items[0])) {
|
|
|
|
$feedItems = [];
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
foreach ($items as $item) {
|
|
|
|
$feedItems[] = new \FeedItem($item);
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
$items = $feedItems;
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
$infos = [
|
|
|
|
'name' => $bridge->getName(),
|
|
|
|
'uri' => $bridge->getURI(),
|
2021-06-25 22:45:25 +03:00
|
|
|
'donationUri' => $bridge->getDonationURI(),
|
2019-02-06 20:34:51 +03:00
|
|
|
'icon' => $bridge->getIcon()
|
|
|
|
];
|
2022-06-22 19:30:06 +03:00
|
|
|
} catch (\Throwable $e) {
|
2019-02-06 20:34:51 +03:00
|
|
|
error_log($e);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-10-31 20:49:45 +03:00
|
|
|
if (logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
|
|
|
|
if (Configuration::getConfig('error', 'output') === 'feed') {
|
|
|
|
$item = new \FeedItem();
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-10-31 20:49:45 +03:00
|
|
|
// Create "new" error message every 24 hours
|
2022-07-08 22:06:14 +03:00
|
|
|
$request['_error_time'] = urlencode((int)(time() / 86400));
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2022-06-22 19:30:06 +03:00
|
|
|
$message = sprintf(
|
|
|
|
'Bridge returned error %s! (%s)',
|
|
|
|
$e->getCode(),
|
2022-07-08 22:06:14 +03:00
|
|
|
$request['_error_time']
|
2019-10-31 20:49:45 +03:00
|
|
|
);
|
2022-06-22 19:30:06 +03:00
|
|
|
$item->setTitle($message);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-10-31 20:49:45 +03:00
|
|
|
$item->setURI(
|
|
|
|
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
|
|
|
. '?'
|
2022-07-08 22:06:14 +03:00
|
|
|
. http_build_query($request)
|
2019-10-31 20:40:51 +03:00
|
|
|
);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-10-31 20:49:45 +03:00
|
|
|
$item->setTimestamp(time());
|
2022-07-08 21:39:13 +03:00
|
|
|
|
|
|
|
$message = sprintf(
|
|
|
|
'Uncaught Exception %s: %s at %s line %s',
|
|
|
|
get_class($e),
|
|
|
|
$e->getMessage(),
|
|
|
|
trim_path_prefix($e->getFile()),
|
|
|
|
$e->getLine()
|
|
|
|
);
|
|
|
|
|
|
|
|
$content = render_template('bridge-error.html.php', [
|
|
|
|
'message' => $message,
|
|
|
|
'stacktrace' => create_sane_stacktrace($e),
|
|
|
|
'searchUrl' => self::createGithubSearchUrl($bridge),
|
|
|
|
'issueUrl' => self::createGithubIssueUrl($bridge, $e, $message),
|
|
|
|
'bridge' => $bridge,
|
|
|
|
]);
|
|
|
|
$item->setContent($content);
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-10-31 20:49:45 +03:00
|
|
|
$items[] = $item;
|
|
|
|
} elseif (Configuration::getConfig('error', 'output') === 'http') {
|
2022-07-08 21:39:13 +03:00
|
|
|
throw $e;
|
2019-10-31 20:49:45 +03:00
|
|
|
}
|
2019-10-31 20:40:51 +03:00
|
|
|
}
|
2019-02-06 20:34:51 +03:00
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2019-02-06 20:34:51 +03:00
|
|
|
$cache->saveData([
|
|
|
|
'items' => array_map(function ($i) {
|
|
|
|
return $i->toArray();
|
|
|
|
}, $items),
|
|
|
|
'extraInfos' => $infos
|
|
|
|
]);
|
|
|
|
}
|
2022-07-01 16:10:30 +03:00
|
|
|
|
2022-07-08 21:39:13 +03:00
|
|
|
$formatFactory = new FormatFactory();
|
|
|
|
$format = $formatFactory->create($format);
|
|
|
|
$format->setItems($items);
|
|
|
|
$format->setExtraInfos($infos);
|
|
|
|
$lastModified = $cache->getTime();
|
|
|
|
$format->setLastModified($lastModified);
|
|
|
|
if ($lastModified) {
|
|
|
|
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $lastModified) . 'GMT');
|
2019-02-06 20:34:51 +03:00
|
|
|
}
|
2022-07-08 21:39:13 +03:00
|
|
|
header('Content-Type: ' . $format->getMimeType() . '; charset=' . $format->getCharset());
|
|
|
|
print $format->stringify();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function createGithubIssueUrl($bridge, $e, string $message): string
|
|
|
|
{
|
|
|
|
return sprintf('https://github.com/RSS-Bridge/rss-bridge/issues/new?%s', http_build_query([
|
|
|
|
'title' => sprintf('%s failed with error %s', $bridge->getName(), $e->getCode()),
|
|
|
|
'body' => sprintf(
|
2022-08-03 18:05:13 +03:00
|
|
|
"```\n%s\n\n%s\n\nQuery string:%s\nVersion:%s\nOs:%s\nPHP version:%s\n```",
|
2022-07-08 21:39:13 +03:00
|
|
|
$message,
|
|
|
|
implode("\n", create_sane_stacktrace($e)),
|
|
|
|
$_SERVER['QUERY_STRING'] ?? '',
|
|
|
|
Configuration::getVersion(),
|
2022-08-03 18:05:13 +03:00
|
|
|
PHP_OS_FAMILY,
|
|
|
|
phpversion() ?: 'Unknown'
|
2022-07-08 21:39:13 +03:00
|
|
|
),
|
|
|
|
'labels' => 'Bridge-Broken',
|
|
|
|
'assignee' => $bridge->getMaintainer(),
|
|
|
|
]));
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function createGithubSearchUrl($bridge): string
|
|
|
|
{
|
|
|
|
return sprintf(
|
|
|
|
'https://github.com/RSS-Bridge/rss-bridge/issues?q=%s',
|
|
|
|
urlencode('is:issue is:open ' . $bridge->getName())
|
|
|
|
);
|
2019-02-06 20:34:51 +03:00
|
|
|
}
|
|
|
|
}
|