Commit Graph
3646 Commits
Author SHA1 Message Date
Ron RichterandClaude Opus 5.5 22c3128b23 Fix escaping of JSON dot notation categories (#9489)
convertJsonToRss() HTML-escapes item fields before rendering the RSS,
but the is_string() guard skipped 'tags', which is always an array.
An unescaped & in a JSON category then produced invalid XML, so
SimplePie rejected the whole document and the feed showed as empty.

Minz_Helper::htmlspecialchars_utf8() already handles arrays,
so the guard is dropped. Add a test for array and string categories.

fix https://github.com/FreshRSS/FreshRSS/issues/9488

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 23:06:29 +02:00
darodi f436b43c82 32-bit systems provide a string for BIGINT (#9486)
Fix #9485
2026-10-22 22:35:47 +02:00
Frans de Jonge 4fcd095a84 Translate new Dutch strings (#9479) 2026-10-21 21:04:43 +02:00
Ricardo SimõesandAlexandre Alapetite f84020a5b4 i18n: Update pt-PT localization (#9473)
* i18n: Update pt-PT localization in feedback.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in admin.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in api.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in conf.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in gen.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in index.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in sub.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in user.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* i18n: Update pt-PT localization in install.php

- Translate missing strings (resolve TODO comments)
- Fix Brazilian Portuguese terminology to European Portuguese (pt-PT)
- Standardize phrasing according to FreshRSS i18n conventions

* Fixed previous word "Link" not translated

* make fix-all + IGNORE

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-21 15:54:02 +02:00
Inverle 0addf32b36 Translate non-English code comments and identifiers (#9383)
Note: The changes in this PR are entirely AI-authored but manually reviewed and tested

## Summary

Translate the remaining French prose and French-derived identifiers in the main FreshRSS/Minz codebase to English.

Why: helps to understand the code

## Scope

- Minz comments, PHPDoc, and paginator identifiers
- Application comments and related identifiers
- Installer database identifiers
- Theme metadata descriptions and the identified CSS comments, previous discussion: https://github.com/FreshRSS/FreshRSS/pull/8241#discussion_r2553181653
- Related call sites and view helpers

External libraries and intentional i18n/docs content are excluded.
2026-10-20 09:52:31 +02:00
Inverle b33749fe6c New hook: Minz_HookType::AutofillField for auth views (#9362)
* New hook: `Minz_HookType::AutofillField` for auth views

Currently to be used in the Demo extension, because it overrides both the formLogin and reauth views (for autofilling username/password), it can be broken every time there is a slight change in core.
For example, after https://github.com/FreshRSS/FreshRSS/pull/9358.

In the future the hook can be extended to support more fields.

Demo extension PR: https://github.com/FreshRSS/xExtension-Demo/pull/6

* Suggested alternative without returning HTML code
2026-10-19 23:10:38 +02:00
spa77kandAlexandre Alapetite c5dd38b0d5 Preserve explicit sort overrides when searching (#9467)
* Preserve explicit sort overrides when searching

Closes #9466

Changes proposed in this pull request:

- Keep an explicit sort override when submitting the header search form, including `c.name`, `f.name` and `lastUserModified`.
- Continue using the already validated `FreshRSS_Context::$sort` value; omit the hidden field when no non-empty override was requested.
- Add header-rendering regression tests for all nine supported sorts, absent/empty overrides and invalid input.

* A bit more fixing

* Fix line mistake

* Forgotten plaintext

* Fix test

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-19 20:27:22 +02:00
Inverle 0d35f6f0ad i18n: pl (#9458) 2026-10-18 22:22:11 +02:00
tomac4tandInverle 859a2ad039 Fix local DNS leak when fetching feeds through a proxy (#9451)
* Fix local DNS leak when fetching feeds through a proxy

Follow-up to #9390: `getRetryAfterFile()` passed the full URL to
`Minz_Request::serverIsPublic()`, which then resolved the target hostname
with the local resolver (`dns_get_record()`), even when a proxy was
configured. With a socks5h/socks4a proxy this leaks the target hostname
to the local DNS and defeats remote name resolution.

- `Minz_Request::serverIsPublic()`: add an `$allowDnsResolve` parameter,
  and forward it to the recursive call so the guard holds for the whole
  recursion.
- `FreshRSS_http_Util::getRetryAfterFile()`: do not resolve the host name
  when a proxy is configured (`$proxy === ''`), since the proxy is
  responsible for DNS.

---------

Co-authored-by: Inverle <inverle@proton.me>
2026-10-17 17:37:27 +02:00
Inverle ed34f6a21d Fix wrong order of args in extension compatibility check (#9445)
See https://www.php.net/manual/function.version-compare.php

The extension's minimum required FreshRSS version must be compared against the currently running version, not the other way around.

Assuming the running FreshRSS version is `1.30.1`:

| Minimum required version | Expected status | Before this fix | After this fix |
|---|---|---|---|
| `1.27.0` | ✅ Compatible | ❌ Incompatible | ✅ Compatible |
| `1.30.1` | ✅ Compatible | ✅ Compatible | ✅ Compatible |
| `1.31.0` | ❌ Incompatible | ✅ Compatible | ❌ Incompatible |
2026-10-15 19:47:06 +02:00
EmreandAlexandre Alapetite 87187136ec docs: updates websub_pending entry in sub.php (#9439)
* Update websub_pending entry in sub.php

Translated the websub_pending attribute entry in sub.php in german

* make fix-all

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-15 16:11:57 +02:00
Alexandre Alapetite f617083328 Mitigate some 404 situations (#9435)
* Mitigate some 404 situations
Fix https://github.com/FreshRSS/FreshRSS/issues/9413 (maybe)
See https://www.php.net/function.session-regenerate-id

* Keep error_code in Session as well

* Ensure login_ok is false
2026-10-15 16:11:21 +02:00
EmreandAlexandre Alapetite f800a4304d docs: updates turkish websub_pending entry in sub.php (#9440)
* docs: updates turkish websub_pending entry in sub.php

Modified the websub_pending attribute entry in sub.php in turkish

* make fix-all

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-14 16:24:52 +02: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
M. Önlük 9e53bdd223 Fix missing left outline on Mark as read button (#9426)
The hidden CSRF input was the first child of the form, directly before the
Mark as read button. Theme rules such as `.stick input + .btn` and
`.stick .btn:first-child` therefore treated the button as the second element
of an input + button group: no left border and no rounded left corners.
Move the input back inside the dropdown, as it was before #9205.

Fixes #9359

Changes proposed in this pull request:

- In `app/layout/nav_menu.phtml`, move the hidden `_csrf` input of the "Mark as read" form back inside the `#nav_menu_read` dropdown (where it was before #9205), instead of being the first child of the form right before the `read_all` button.
- Because of that input, many theme rules written for `input + .btn` groups (`.stick input + .btn`, and `.btn:first-child` no longer matching) removed the left border and the rounded left corners of the "Mark as read" button. This affected Alternative Dark, Origine and the themes built on them (Dark, Dark-pink, Origine-compact), Pafat, and gave a stray separator and square corners in Flat, Ansum and Mapco.
2026-10-12 09:21:10 +02:00
0187b15c8e Sanitizer: block data: URI scheme while keeping data:image for images (#9417)
* Sanitizer: block data: URI scheme, keep data:image for images

FreshRSS_SimplePieCustom only disallowed the `javascript` scheme, so
`data:text/html,...` URIs in feed content survived
sanitization on <a href>, <iframe src>, <audio|video|source|track src>.
The default CSP neutralises these on FreshRSS pages, but clients and
deployments without that CSP (API WebViews, extensions using amendCsp(),
proxies stripping headers) could execute them. Same class as
GHSA-3fw6-j8m4-82vj / #9272.

- Sanitize::is_allowed_uri(): element/attribute-aware check that still
  permits inert `data:image/...` on img[src], image[src], video[poster]
  while blocking `data:` elsewhere; replace_urls() now uses it.
  is_allowed_scheme() is unchanged for backward compatibility.
- SimplePieCustom: disallow_uri_schemes(['javascript','data']).
- Tests: 4 blocked-input cases, a data:image keep-alive test and an
  `unsafe:` prefix test. They fail without the patch and pass with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Sync SimplePie
https://github.com/FreshRSS/simplepie/pull/92

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-11 20:36:09 +02:00
Alexandre Alapetite c7c53f4576 WebSub fix POST, fix warning, better message (#9420)
* WebSub fix warning and better message
Contributes to https://github.com/FreshRSS/FreshRSS/issues/9416

* Fix POST conditions
2026-10-09 22:28:18 +02:00
Alexandre Alapetite 4a3c6b6b60 Fix PHP 8.5+ deprecation sqliteCreateFunction (#9421)
Fix https://github.com/FreshRSS/FreshRSS/issues/8525#issuecomment-6065641500
2026-10-08 22:55:00 +02:00
abp002andAlejandro Borrego 48547b4a6c Reduce memory consumption of export (#9350)
Contributes to #6139 (export part; the import will follow in another PR)

Exporting favourites was collecting the whole file in memory. With many articles, it fails with the default PHP memory limit.

## Changes
- New `Minz_View::helperToFile()`, similar to `helperToString()`, writing the output of a helper into a file by chunks of 64 KiB instead of keeping it in memory.
- `FreshRSS_Export_Service`: the `generate*()` methods now return the path of a temporary file instead of the content, and `zip()` builds the archive from those files (`ZipArchive::addFile()` instead of `addFromString()`). Temporary files are created in `TMP_PATH` (already required to be writable) and deleted at the end of the script.
- The export is sent with `readfile()`: Web UI, `cli/export-zip-for-user.php`, `cli/export-opml-for-user.php` and the OPML export of the Google Reader API.
- Note: this changes what `FreshRSS_Export_Service` returns (a path instead of the content). All callers in the repository are updated, and I could not find any public extension using this service.

## Measurements
Peak PHP memory (`memory_get_peak_usage(true)`), SQLite database with synthetic starred articles:

| Scenario | Before | After |
|---|---|---|
| Web, export your favourites, 10,000 articles (69 MB JSON) | 136 MB (fails with the default `memory_limit` of 128 MB) | 6 MB |
| Web, ZIP (OPML + favourites + labelled articles + 4 feeds), 10,000 articles | 152 MB | 6 MB |
| CLI `export-zip-for-user.php`, 10,000 articles | 189 MB (fails with 128 MB) | 8 MB |
| Web, export your favourites, 30,000 articles (208 MB JSON) | 404 MB | 10 MB |

Co-authored-by: Alejandro Borrego <210245044+abp002@users.noreply.github.com>
2026-10-05 21:47:19 +02:00
Alexandre Alapetite c7396a3a68 Update to GT\CssXPath 1.6.0 (#9411)
https://github.com/phpgt/CssXPath/releases/tag/v1.6.0
2026-10-05 21:44:57 +02:00
akinechanandakinechan 34c60510f2 i18n(ja): translate the remaining TODO strings (#9398)
Translate the last untranslated Japanese strings and mark the Lithuanian
endonym as IGNORE, consistent with the other language names.

Co-authored-by: akinechan <1593847+akine@users.noreply.github.com>
2026-10-04 18:35:07 +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
Nikolai ShcheglovandAlexandre Alapetite a93bd27b7d Fix muting of feeds gone with HTTP 410 (#9391)
* Fix muting of feeds gone with HTTP 410

A feed answering HTTP 410 Gone is meant to be muted (#3561), but:

* Since #6037, FreshRSS_Feed_Exception extends Minz_Exception, which
  resets any code other than its own error levels to 0, so the check for
  410 never matched. FreshRSS_Feed_Exception now keeps the HTTP status
  code it is given.
* FreshRSS_FeedDAO::mute() mutes by negating the TTL, which leaves a feed
  with the default TTL (0) unmuted. Mute the feed the way the feed
  settings and dynamic OPML categories do instead.

* A bit of cleaning

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-10-03 09:59:51 +02:00
OrangeJuice a4dbc4240d i18n(de): follow-up to #9370 review; add Max Oberrauch to credits (#9385)
Follow-up to #9370, applying @math-GH's review:

- `latest`: „Veröffentlichte Version“ instead of „Versionierte Veröffentlichung“. I didn't go back to „Stabile Version“ because #9270 moved the English away from "stable" on purpose, as @Inverle pointed out; „Veröffentlichte Version“ pairs with „Aktueller Entwicklungsstand“ for edge.
- „berücksichtigen“ instead of „einbeziehen“, in both include labels.
- `global_hint` without „Sie“, as suggested.
2026-10-02 10:16:31 +02:00
OrangeJuice 573078baa3 i18n(de, it): translate remaining TODO strings and review DIRTY ones (#9370) 2026-10-01 20:07:32 +02:00
Alexandre Alapetite e247edb1fb Fixes for PHPStan 2.2.16 (#9368)
Bump other dev dependencies as well, prior to upcoming release 1.30.1
2026-09-30 15:49:06 +02:00
Alexandre Alapetite a8e5e6300b Better enforce feed limits (#9357)
E.g. when added from API
Fix https://github.com/FreshRSS/FreshRSS/issues/8822#issuecomment-5781019651
2026-09-28 10:41:29 +02:00
5a270c02b0 Rotate session ID on all authenticated transitions (CWE-384) (#9333)
* auth: rotate session ID on all authenticated transitions (session fixation)

The PHP session ID was regenerated on explicit form login, reauth, logout and
password change, but NOT when access is (re)established from a remember-me
cookie, via HTTP authentication, or on self-registration auto-login (CWE-384),
so a fixed session ID could become authenticated.

Regenerate the session ID at those transitions too: in FreshRSS_Auth::init
when accessControl() first grants access (covers remember-me restore and HTTP
auth; skipped for auth_type 'none', which has no authentication boundary), and
in the self-registration auto-login path, mirroring the existing form-login
idiom. Also enable session.use_strict_mode before session_start as defence in
depth so an uninitialised, attacker-supplied session ID is not adopted.

* auth: avoid duplicate cookie during session rotation

* Code preferences, shorten some comments

---------

Co-authored-by: Inverle <inverle@proton.me>
Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-28 10:39:21 +02:00
GigiandAlexandre Alapetite cd42dc7509 Bound ZIP import against decompression bombs (CWE-409) (#9332)
* importExport: bound ZIP import against decompression bombs

ZIP import read every archive member into memory via getFromIndex() with no
limit on member count, per-member or total uncompressed size, or compression
ratio, so a small crafted upload could exhaust memory (CWE-409).

Enforce a member-count limit, per-member and aggregate uncompressed-size
limits and a compression-ratio ceiling from the central-directory metadata,
and additionally cap the real decompressed size while streaming each member
(the declared size is attacker-controlled and can under-report). Oversized or
over-compressed members are skipped and the loop stops once the budget is
reached; a realistic export (an OPML plus a few JSON files) stays well within
the limits.

* Fix lookup by name and total calculation

* Fix locateName regression
Duplicate entry name: keep only the first occurrence, but allow full paths

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-27 21:25:08 +02:00
Gigi d5ad610c3d Make the login challenge nonce one-time (CWE-294) (#9334)
* auth: make the login challenge nonce one-time

The per-login challenge nonce stored in the session was never consumed or
cleared, so a captured challenge stayed replayable and survived logout
(CWE-294). The client (p/scripts/extra.js) already fetches a fresh nonce for
every attempt, so consuming it does not affect legitimate retries.

Consume the nonce (unset it) as soon as it is read for verification in form
login, reauthentication, password change and account self-deletion, and clear
it in FreshRSS_Auth::removeAccess so logout invalidates any outstanding
challenge.

* auth: don't clear the nonce in removeAccess (breaks login)

removeAccess() runs on every unauthenticated request via FreshRSS_Auth::init()
-- including the login POST itself, before formLoginAction reads the nonce -- so
clearing it there rejected every login with 'Invalid session'. Thanks
@jamalkamaladdin for the measured catch. The one-time guarantee is preserved by
consuming the nonce when it is read for verification (login/reauth/password
change/self-deletion); logout no longer needs a separate clear.
2026-09-27 12:21:17 +02:00
GigiandAlexandre Alapetite a6253486c0 Require POST+CSRF for self-update mutations; guard post-config step (CWE-352) (#9335)
* update: require POST + CSRF for updater mutations; guard post-config step

The self-update check/apply mutations were reachable via GET, which is never
anti-CSRF validated (the token is only checked on POST), and the privileged
post-configuration step (do_post_update()/PostUpdate hook, removal of the
staged update file) was additionally reauth-exempt and GET-reachable (CWE-352).

- checkAction and the apply branch now require POST, so the global anti-CSRF
  token check applies; the update page's Check/Apply controls are POST forms
  carrying the token.
- The post_conf finalisation is bound to a one-time session marker set by the
  successful apply right before its internal redirect, so it only runs as that
  continuation, not via a direct cross-site GET (independent of reauth config).
- Reauth is now required consistently, including for apply/post_conf (was
  exempted); the legitimate post_conf redirect still passes on the reauth done
  moments earlier by the apply request.

Status/index endpoints remain GET and read-only.

* Reduce some comments

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-26 17:02:22 +02:00
Inverle 6153087f68 Fix regression with HTTPS proxies due to wrong TLS SNI resolution (#9341)
Fixes #9314
More details: https://github.com/FreshRSS/FreshRSS/issues/9314#issuecomment-5750644306

Short version is that I forgot to consider CURLOPT_RESOLVE, since I was focused on making sure that socks4a and socks5h behaved correctly.

SimplePie fork PR: https://github.com/FreshRSS/simplepie/pull/91

(there would be problems only with some HTTPS proxies, such as the one described in the issue)
2026-09-26 15:54:16 +02:00
Ethan Stoner 5fb62464e9 Call check_url_before_add hook when previewing a feed (#9343)
The GET branch of feedController::addAction() built a FreshRSS_Feed
straight from the requested URL and loaded it, without invoking the
Minz_HookType::CheckUrlBeforeAdd hook. Only the POST branch, through
addFeed(), ran the hook.

Extensions that turn a website URL into a feed URL (e.g. RSS-Bridge)
were therefore bypassed on the confirmation page reached from the
bookmarklet: the raw website URL was probed, and since such sites do
not advertise a feed, the preview reported that no feed could be
added. Submitting the very same form then succeeded, because the POST
path did apply the hook.

Run the hook at the start of the GET branch so the preview probes the
URL that would actually be subscribed to, and reuse the existing
"not added" feedback when an extension refuses the URL, matching what
addFeed() does when the hook returns null.

The documented contract is that the hook "will be executed every time
a URL is added", so the preview should not be an exception.

Fixes #9339
2026-09-26 15:24:10 +02:00
03d5797d9f i18n(tr): translate remaining Turkish TODO strings (#9349)
* i18n(tr): translate remaining TODO strings

* i18n(tr): update translation status in READMEs

---------

Co-authored-by: qwist1233-cpu <qwist1233-cpu@users.noreply.github.com>
Co-authored-by: alperen <qwist1233@users.noreply.github.com>
2026-09-26 15:15:06 +02:00
colons b0ccfe8b24 Don't disable anonymous refresh when anonymous feed access is disabled. (#9354)
Closes #9353; reverts part of 2701b7dff4 https://github.com/FreshRSS/FreshRSS/pull/9235
2026-09-26 15:06:57 +02:00
Pavel Miniutka fcc41b6ea0 Add Belarusian (be) translation (#9351)
Complete interface translation under app/i18n/be/. The locale is
registered in all app/i18n/*/gen.php, .typos.toml, docs and READMEs.
2026-09-26 14:57:04 +02:00
Alexandre Alapetiteandkta1kri 64f7f24c6e Improve WebSub security (#9330)
* Improve WebSub security
New keys are now more random

* Refuse to downgrade from HTTPS to HTTP

Co-authored-by: kta1kri <77656895+kta1kri@users.noreply.github.com>
2026-09-20 17:25:43 +02:00
Alexandre Alapetite 8067fd994b Use PHP SensitiveParameter (#9322)
https://www.php.net/class.sensitiveparameter
Active from PHP 8.2+ but parses without effect on PHP 8.1
2026-09-19 19:52:07 +02:00
Alexandre Alapetite ad4723cb1f Warn during session regenerate fail (#9311)
Might provide more logs for e.g. https://github.com/FreshRSS/FreshRSS/discussions/9305
2026-09-19 14:33:25 +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
Morax cc45db5ef8 Fix adaptive reading state after marking articles read (#9290)
Closes #9288

Changes proposed in this pull request:

The mark-as-read links contain the resolved state, so the automatic reading preference becomes an explicit `state=2` filter after the redirect. Once the last unread article is marked read, the page stays empty instead of showing all articles.

- Keep the requested state separately from the resolved state used by the action. A redirect state of `0` reapplies the reading preference; explicit filters still survive the redirect as intended by #9007.
- Carry that value through the menu, footer and “Load more” link. This matters with oldest-first pagination, where the last footer supplies the menu button's action.
- Add request-state and rendered-pagination tests, plus a changelog entry.

How to test the feature manually:

1. With both read and unread articles, choose “Show unreads if any, all articles otherwise”. Mark all as read from the footer, then repeat using the top button. The resulting page should show the read articles.
2. Set one article per page and oldest-first sorting. Load the remaining articles before marking all as read; the same fallback should work.
3. Select an explicit unread filter: marking everything read should still leave an empty unread view. Also check a favourite filter with a search term, including “Mark selection unread”; the search and favourite scope should remain intact.

Validation: `make NO_DOCKER=1 fix-all` and `make NO_DOCKER=1 test-all` pass with PHP 8.5.9 (719 PHPUnit tests, 1,378 assertions). Browser checks passed against SQLite in Chromium (desktop and mobile viewport) and Firefox. The original footer fails three of the six new rendered-pagination cases.
2026-09-11 08:18:09 +02:00
Alexandre Alapetite 61d7925af1 Config + increase default values for search max length and depth (#9280)
Fix https://github.com/FreshRSS/FreshRSS/discussions/9279
Follow-up of https://github.com/FreshRSS/FreshRSS/pull/9277

Values can be overridden in `data/config.php`, or in `./data/config.custom.php` before the install process
2026-09-10 22:14:55 +02:00
AdamKorczandAlexandre Alapetite dc5cf8221b Limit the length and parentheses nesting depth of a search query (#9277)
* Limit the length and parentheses nesting depth of a search query.

* Use exception

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-09 16:12:41 +02:00
Ali GündoğarandAlexandre Alapetite 970b135190 Ignore security-relevant OPML attributes for dynamic OPML sources (#9276)
* Ignore security-relevant OPML attributes for dynamic OPML sources

OPML content can come from an untrusted source (a dynamic OPML category re-fetches its remote content on every refresh cycle). Such content must not be able to configure feed cURL parameters (CURLOPT_COOKIE, CURLOPT_PROXY, CURLOPT_POSTFIELDS, etc.) nor create further dynamic OPML categories. importOpml() therefore defaults to untrusted, and the local file import controller explicitly opts in.

* Review

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-09 16:11:38 +02:00
Inverle 7b6ec0f8d6 Log invalid challenge and ignore JSON error during login (#9278)
* Log and block invalid challenge formats during login

Fixes some unexpected behaviors during a user login, including strange errors in the log related to JSON.

* Ignore JSON decode errors for URL unserializing

* Document the format a bit
2026-09-09 16:11:05 +02:00
Ali GündoğarandAlexandre Alapetite 18dfada959 Only allow http/https URI schemes for enclosure and thumbnail URLs (#9272)
* Only allow http/https URI schemes for enclosure and thumbnail URLs

Feed enclosure URLs were stored and rendered without URI scheme validation:
SimplePie's scheme check only applies to URLs inside content HTML
(Sanitize::replace_urls()), not to enclosure metadata, so a `javascript:`
URL survived the whole pipeline and was served as a clickable link in the
article view, including through the Google Reader compatible API.

Add FreshRSS_http_Util::isAllowedUrlScheme() and apply it:
- at store time, when building enclosure attributes in FreshRSS_Feed
- at render time, in FreshRSS_Entry::content(), so entries already stored
  in existing databases are protected as well

https://github.com/FreshRSS/FreshRSS/security/advisories/GHSA-3fw6-j8m4-82vj

* Reuse existing SimplePie is_remote_uri() function

* Use more realistic test examples

* Add tests for protocol relative
And simplify tests further

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-09 00:14:13 +02:00
InverleandAlexandre Alapetite 58e32add92 SimplePie: Harden cache deserialization calls (#9273)
* SimplePie: Harden cache deserialization calls

To reduce impact if a maliciously provided cache file is somehow loaded, e.g. from a manipulated backup or some kind of external arbitrary file write vulnerability.

* Sync our SimplePie

https://github.com/FreshRSS/simplepie/pull/90

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
2026-09-08 23:36:59 +02:00
Alexandre AlapetiteandInverle cea88ace69 Reword explanations for edge/latest channels (#9270)
* Reword explanations for edge/latest channels
Highlight the fact that we do not backport bug fixes or security fixes to previous releases.
Simplify also a slightly obsolte MySQL doc to favour SQLite

* Merge i18n: az

* Update Polish translations to specify rolling/versioned releases

Co-authored-by: Inverle <inverle@proton.me>

* Update 05_Release_new_version.md

---------

Co-authored-by: Inverle <inverle@proton.me>
2026-09-08 23:25:18 +02:00
Jamal Ali 65e402ca41 i18n: add Azerbaijani translation (#9269) 2026-09-08 14:42:42 +02:00