7 Commits
Author SHA1 Message Date
Nikolai ShcheglovandAlexandre Alapetite acde1bfbf5 Fix non-ASCII characters in scripts of fetched HTML pages (#9384)
* Fix non-ASCII characters in scripts of fetched HTML pages

To add a <base> to a fetched HTML page, enforceHtmlBase() re-serialises it
with libxml2 saveHTML(). For a whole document, libxml2 2.9 only knows the
charset from <meta http-equiv="Content-Type">: with <meta charset="UTF-8">
or a byte order mark, it writes every non-ASCII character as an HTML entity.
In text that is harmless, but inside <script> entities are not decoded, so
e.g. the JSON read by HTML + XPath + JSON feeds gets "Wall Street&rsquo;s"
instead of "Wall Street’s", which then shows in titles.

UTF-8 documents are now serialised node by node, which writes UTF-8.
Other charsets keep the previous output.

* Clarify comment about the byte order mark

* Remove comment about the byte order mark

* Minor test syntax preference

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-31 14:48:37 +01:00
Alexandre Alapetite fd3f75b3a2 Require PHP 8.2+ (#9470)
Drop PHP 8.1 (EOL December 2025), drop Ubuntu 22.04 (EOL May 2027, and there is now 24.04 and 26.04 LTS).
PHP 8.1 is already EOL, and Debian, Alpine, Fedora, Red Hat, Synology have dropped PHP 8.1 already.
See https://github.com/FreshRSS/FreshRSS/discussions/5474#discussioncomment-16791937

With PHP 8.6 around the corner and in particular a relevant `FILTER_FLAG_GLOBAL_RANGE` in PHP 8.2+.
* https://www.php.net/releases/8.2
* https://www.php.net/filter.constants.validation#constant.filter-flag-global-range
* https://www.php.net/class.sensitiveparameter
* https://php.watch/versions/8.2/readonly-classes
* https://www.php.net/function.ini-parse-quantity

Allows updating to PHPUnit 11.x instead of 10.x
https://phpunit.de/supported-versions.html

Contributes to https://github.com/FreshRSS/FreshRSS/issues/9475

* Add regression test for FILTER_FLAG_GLOBAL_RANGE
https://github.com/FreshRSS/FreshRSS/pull/9470#discussion_r4371168720
2026-10-26 21:02:32 +01:00
Alexandre Alapetite 7e8ef628d9 Better doc and log proxy cases (#9436)
Fix https://github.com/FreshRSS/FreshRSS/issues/9423
And strip redundant proxy scheme.
And add a few more unit tests.
2026-10-13 19:50:38 +02:00
d5e13066bc Fix NAT64 feed fetching on IPv6-only hosts (#9372)
* Fix NAT64 feed fetching on IPv6-only hosts

Closes #9371
Regression from https://github.com/FreshRSS/FreshRSS/pull/9195

Changes proposed in this pull request:

* Fix feed fetching on IPv6-only hosts using NAT64.
* Preserve SSRF protection by validating the IPv4 address embedded in RFC 6052 NAT64 addresses before allowing it in `CURLOPT_RESOLVE`.
* Add regression tests for both public and private/reserved NAT64 addresses.

* Minor formating

* Add test tearDown

* RFC8215 64:ff9b:1::/48
Credits: @onurcangnc

---------

Co-authored-by: Viktor Platov <TapuGitHub@users.noreply.github.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-03 21:24:33 +02:00
Nikolai ShcheglovandAlexandre Alapetite f65d2760e3 Fix domain-wide Retry-After (#9390)
* Fix domain-wide Retry-After

- Fix the domain-wide HTTP `Retry-After` for public servers, which has not applied since #8195 (FreshRSS 1.28.0).
  - `getRetryAfterFile()` passed the bare host name to `Minz_Request::serverIsPublic()`. That function expects a URL, so it always returned `false`.
  - So a 429 or 503 paused only the URL that got it, despite the log saying "For that domain, will first retry after …", and FreshRSS went on requesting the other feeds of that server. With several Reddit feeds, for instance, each one got its own 429 in turn.
  - `serverIsPublic()` now gets the URL. Its answer is kept per host for the rest of the request, because it resolves the host name (`dns_get_record()`), and a refresh may load many feeds of the same host.
- Fix the per-URL `Retry-After` of servers on a local network (#8195), which never applied to feeds whose URL, as stored, differs from the URL as requested.
  - Stored URLs are HTML-encoded, e.g. `&amp;` in an RSS-Bridge URL, and may end with `#force_feed`.
  - `FreshRSS_Feed::load()` checked the stored URL, while the pause is stored for the URL as requested. It now checks the requested URL, as `loadJson()` and `loadHtmlXpath()` already do.

* Minor final

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-03 10:22:47 +02:00
Jamal Kamaladdinoglu 2bb901d9c4 Accept a trusted proxy address given without a subnet (#9301)
TRUSTED_PROXY=192.168.1.1 is ignored and only 192.168.1.1/32 is honoured. checkCIDR() splits the range on the slash and rejects the value when the mask part is missing, so an address written without a subnet never matches.

An address without a subnet is now read as a single host: /32 for IPv4 and /128 for IPv6. That is the format docs/en/admins/09_AccessControl.md points at.

Host names such as gateway.localdomain are still not resolved.

tests/app/Utils/httpUtilTest.php gains 16 cases for checkCIDR, two of which fail without this change.

Refs #9210
2026-09-13 22:34:39 +02:00
7744f1fed9 WebSub: ignore http/https scheme difference in Self URL comparison (#9005)
* WebSub: ignore http/https scheme difference in Self URL comparison

The PubSubHubbub endpoint logged 'Self URL does not match registered
canonical URL' whenever a feed's self URL differed from the registered
canonical URL only by http vs https (common with http->https redirects),
spamming the log. Compare the URLs ignoring the http/https scheme while
still requiring host/path/query to match exactly.

Closes #3087

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Stylistic preferences

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-07-09 07:50:42 +02:00