Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions xExtension-RssBridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ To use it, upload this entire directory to the FreshRSS `./extensions` directory
## Configuration settings

* `rss_bridge_url`: the URL for an RSS-Bridge instance e.g. `https://example.com/rss-bridge/`
* `rss_bridge_token`: (optional) authentication token for secured RSS-Bridge instances

## Bridge availability

Expand Down
7 changes: 7 additions & 0 deletions xExtension-RssBridge/configure.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
</div>
</div>

<div class="form-group">
<label class="group-name" for="rss_bridge_token"><?php echo _t('ext.rssbridge.token'); ?></label>
<div class="group-controls">
<input type="text" name="rss_bridge_token" id="rss_bridge_token" value="<?php echo FreshRSS_Context::$system_conf->rss_bridge_token; ?>" placeholder="Optional authentication token">
</div>
</div>

<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
Expand Down
112 changes: 106 additions & 6 deletions xExtension-RssBridge/extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,119 @@ public function handleConfigureAction() {
if (Minz_Request::isPost()) {
FreshRSS_Context::$system_conf->rss_bridge_url =
Minz_Request::param('rss_bridge_url', '');
FreshRSS_Context::$system_conf->rss_bridge_token =
Minz_Request::param('rss_bridge_token', '');
FreshRSS_Context::$system_conf->save();
}
}

public static function RssBridgeDetect($url) {
$bridge = FreshRSS_Context::$system_conf->rss_bridge_url .
$bridge_url = FreshRSS_Context::$system_conf->rss_bridge_url .
'?action=detect&format=Atom&url=' . rawurlencode($url);

if(strpos(get_headers($bridge)[0], '301')) {
return $bridge;

// Add token if configured
$token = FreshRSS_Context::$system_conf->rss_bridge_token ?? '';
if (!empty($token)) {
$token = 'token=' . rawurlencode($token);
$bridge_url .= '&' . $token;
}
else {
return $url;

// Get both headers and response body
$context = stream_context_create([
'http' => [
'timeout' => 10,
'follow_location' => false, // Don't follow redirects automatically
'ignore_errors' => true // Get content even on error status codes
]
]);
$response_body = file_get_contents($bridge_url, false, $context);
$headers = $http_response_header ?? [];

if (empty($headers)) {
Minz_Log::warning('[RSS-Bridge extension] Failed to connect to RSS-Bridge at ' . FreshRSS_Context::$system_conf->rss_bridge_url);
} else if (strpos($headers[0], '301') !== false) {
// Parse Location header from response headers array
$redirect_url = null;
foreach ($headers as $header) {
if (stripos($header, 'location:') === 0) {
$redirect_url = trim(substr($header, 9)); // Remove "location:" prefix
break;
}
}

if ($redirect_url) {
// Make relative URLs absolute
if (strpos($redirect_url, 'http') !== 0) {
$redirect_url = FreshRSS_Context::$system_conf->rss_bridge_url . $redirect_url;
}

// If we have a token and the redirect URL doesn't contain it, add it
if (!empty($token) && strpos($redirect_url, 'token=') === false) {
$separator = strpos($redirect_url, '?') !== false ? '&' : '?';
$redirect_url .= $separator . $token;
}

// Parse bridge name from redirect URL
$bridge_name = '';
if (preg_match('/[?&]bridge=([^&]+)/', $redirect_url, $matches)) {
$bridge_full_name = $matches[1];
// Remove 'Bridge' suffix if it exists, otherwise use full name
if (strtolower(substr($bridge_full_name, -6)) === 'bridge') {
$bridge_name = substr($bridge_full_name, 0, -6);
} else {
$bridge_name = $bridge_full_name;
}
$bridge_name .= ' ';
}

Minz_Log::warning('[RSS-Bridge extension] ' . $bridge_name . 'URL detected! Created feed URL: ' . $redirect_url);
return $redirect_url;
} else {
Minz_Log::warning('[RSS-Bridge extension] Got 301 redirect but no Location header found');
}
} else {

// Extract error message from HTML response <p> tag
$error_message = '';
if (!empty($response_body) && preg_match('/<p[^>]*>(.*?)<\/p>/s', $response_body, $matches)) {
$error_message = trim(strip_tags($matches[1]));
}

// Log specific response cases with extracted error messages
if (strpos($headers[0], '401') !== false) {
$log_msg = '[RSS-Bridge extension] Authentication failed: ';
if ($error_message && stripos($error_message, 'token') !== false) {
$log_msg .= $error_message;
} else {
$log_msg .= 'Server uses an unsupported authentication method'; // (' . $error_message . ')
}
Minz_Log::warning($log_msg);
} else if (strpos($headers[0], '404') !== false) {
Minz_Log::warning('[RSS-Bridge extension] (404) RSS-Bridge endpoint not found - check configuration');
} else if (strpos($headers[0], '200') !== false) {
// Log when RSS-Bridge can't handle the URL (comment out if too noisy)
$log_msg = '[RSS-Bridge extension] ' . ($error_message ?: '(200) RSS-Bridge unable to handle request for unknown reason');
Minz_Log::warning($log_msg);
} else {
// Check if this was actually a connection failure
if ($response_body === false) {
$error = error_get_last();
$error_message_raw = $error['message'] ?? 'unknown reason';
Minz_Log::warning('[RSS-Bridge extension] Connection to RSS-Bridge endpoint failed: ' . $error_message_raw);
} else {
// Log other unexpected responses
$log_msg = '[RSS-Bridge extension] Unexpected response: ' . trim($headers[0]);
if ($error_message) {
$log_msg .= ' (message: ' . $error_message . ')';
}
Minz_Log::warning($log_msg);
}
}
}

// Log that we're passing through the original URL (comment out if too noisy)
Minz_Log::warning('[RSS-Bridge extension] Passing through original URL: ' . $url);

return $url;
}
}
1 change: 1 addition & 0 deletions xExtension-RssBridge/i18n/en/ext.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
return array(
'rssbridge' => array(
'url' => 'RSS-Bridge URL',
'token' => 'Authentication Token',
),
);
4 changes: 2 additions & 2 deletions xExtension-RssBridge/metadata.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "RSS-Bridge",
"author": "Devon Hess",
"description": "Run URLs through <a href=\"https://github.com/rss-bridge/rss-bridge\">RSS-Bridge</a> detection.",
"version": 1.1,
"description": "Run URLs through <a href=\"https://github.com/rss-bridge/rss-bridge\">RSS-Bridge</a> detection with token authentication support.",
"version": 1.2,
"entrypoint": "RssBridge",
"type": "system"
}