Files
ttrss-plugin-af-fulltext/lib/TtrssFetcher.php

49 lines
1.6 KiB
PHP
Raw Normal View History

<?php
namespace AfFulltext;
/**
* The `direct` backend inside tt-rss.
*
* Deliberately goes through UrlHelper rather than cURL directly, so article
* fetches keep tt-rss's own SSRF protection (UrlHelper::validate refuses private
* and link-local addresses). Feeds are attacker-influenced input; a plugin that
* fetches whatever a feed points at must not be the hole in that.
*
* FirecrawlFetcher is the deliberate exception -- it talks to one operator-configured
* in-cluster endpoint, which UrlHelper would refuse precisely because it is private.
*/
final class TtrssFetcher implements Fetcher {
public function __construct(private readonly int $timeout = 20) {}
public function name(): string { return 'direct'; }
public function fetch(string $url, array $headers = []): FetchResult {
if (!class_exists('\UrlHelper'))
return new FetchResult(error: 'UrlHelper unavailable (not running inside tt-rss)');
$options = [
'url' => $url,
'http_accept' => 'text/*',
'type' => 'text/html',
'timeout' => $this->timeout,
];
// Site configs routinely set a user-agent to get the article rather than a
// consent wall; UrlHelper takes it as a first-class option.
if (isset($headers['user-agent'])) $options['useragent'] = $headers['user-agent'];
$body = \UrlHelper::fetch($options);
if (!is_string($body) || $body === '') {
$error = \UrlHelper::$fetch_last_error ?: 'fetch returned nothing';
return new FetchResult(error: $error, status: (int) \UrlHelper::$fetch_last_error_code);
}
return new FetchResult(
html: $body,
effective_url: \UrlHelper::$fetch_effective_url ?: $url,
status: 200,
);
}
}