Skip to content
Merged
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
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@ permissions:

jobs:
php:
name: PHP
name: PHP (FreshRSS ${{ matrix.freshrss }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Both ends of the supported range. Analysing only the development tip is how a call to
# a core method that exists solely on edge -- FreshRSS_http_Util::getCurlResolveInfo(),
# added after 1.29.1 -- reached users as a fatal error on every released version.
freshrss: [edge, '1.29.0']
steps:
# This extension cannot be analysed on its own: every class it touches
# (Minz_Extension, FreshRSS_Feed, …) and the global helpers (_t, _i, _url)
Expand All @@ -20,7 +27,7 @@ jobs:
uses: actions/checkout@v7
with:
repository: FreshRSS/FreshRSS
ref: edge
ref: ${{ matrix.freshrss }}
path: FreshRSS

- name: Check out this extension inside it
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ push as a feed.
This runs alongside core's WebSub support and shares nothing with it. A feed advertising both a hub
and a cloud will be subscribed through both.

## Requirements

**FreshRSS 1.29.0 or newer.** The binding constraint is `Minz_HookType::FeedsListBeforeActualize`,
which core added in that release.

One behaviour is version-dependent rather than required. When walking a feed's permanent redirects,
each hop is re-checked against the internal-host allowlist through
`FreshRSS_http_Util::getCurlResolveInfo()`, which core added after 1.29.1. Cores without it skip
that check and probe without DNS pinning — the same exposure those versions already carry for every
feed fetch, since their `httpGet()` did not pin DNS either.

## Discovery

| Resource | Advertisement | Read from |
Expand Down
15 changes: 10 additions & 5 deletions RssCloud/Redirects.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,16 @@ private static function permanentLocation(string $url, array $attributes): strin
return false;
}

// Re-checked at every hop, so a redirect cannot walk into the private network.
$resolve = FreshRSS_http_Util::getCurlResolveInfo($url);
if (!is_array($resolve)) {
// null: the host's IP is not in the allowlist. false: the host did not resolve.
return false;
// Re-checked at every hop, so a redirect cannot walk into the private network. Cores older than
// FreshRSS 1.29.2 have no such check to offer — `httpGet()` did not pin DNS there either, so the
// probe goes ahead unpinned rather than disabling redirect resolution on every released version.
$resolve = [];
if (method_exists(FreshRSS_http_Util::class, 'getCurlResolveInfo')) {
$resolve = FreshRSS_http_Util::getCurlResolveInfo($url);
if (!is_array($resolve)) {
// null: the host's IP is not in the allowlist. false: the host did not resolve.
return false;
}
}

$ch = curl_init();
Expand Down
18 changes: 18 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,21 @@ parameters:
scanDirectories:
- ../../app
- ../../lib

# `getCurlResolveInfo()` arrived after 1.29.1, so whether it exists depends on which core is
# checked out alongside -- something PHPStan cannot model from one checkout. Whichever core it
# analyses, it calls the runtime guard redundant, just from opposite directions. Both entries
# are listed so a single config serves either; `reportUnmatchedIgnoredErrors: false` above lets
# the inapplicable one pass unmatched. Drop the guard and this whole block once 1.29.2 is the
# oldest supported release.
ignoreErrors:
-
# Against edge, which has the method.
identifier: function.alreadyNarrowedType
message: '#Call to function method_exists\(\) with .FreshRSS_http_Util. and .getCurlResolveInfo. will always evaluate to true\.#'
path: RssCloud/Redirects.php
-
# Against 1.29.1 and older, which do not.
identifier: function.impossibleType
message: '#Call to function method_exists\(\) with .FreshRSS_http_Util. and .getCurlResolveInfo. will always evaluate to false\.#'
path: RssCloud/Redirects.php