Skip to content

Fix/273 hardening - #179

Merged
mambax7 merged 10 commits into
XOOPS:masterfrom
mambax7:fix/273-hardening
Aug 24, 2026
Merged

Fix/273 hardening#179
mambax7 merged 10 commits into
XOOPS:masterfrom
mambax7:fix/273-hardening

Conversation

@mambax7

@mambax7 mambax7 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Why

What

Verification

Checklist

Notes for the items marked "see notes": CONTRIBUTING.md - Pull request checklist notes

  • docs/changelog.270.txt has an entry (the root CHANGELOG.md is generated - never hand-edit it)
  • Changed conditionals have both branches covered by a test, including the failure paths
  • New or changed tests pass with their file run alone and leave no process-global state behind (constants need process isolation - see notes)
  • No diagnostic, log line, or error message emits a full server path - basename() or root-relative only
  • Production code handles failures explicitly - no unchecked @ suppression, results of calls like ini_set() checked (see notes)
  • Factual claims in the description, comments, and changelog were verified by running them, not inferred
  • The description matches the implementation - a contract change during review updates it too
  • Commits use the conventional-commit dialect in use, fixups squashed (see notes)

Summary by Sourcery

Harden XOOPS 2.7.3 against security threats, improve production reliability, and prepare the core for PHP 8.6 while adding optional editor and debugging enhancements.

New Features:

  • Add optional SCEditor BBCode support and unify the DHTML toolbar across form renderers.
  • Introduce file-based debug configuration and rotating, redacting file logging.
  • Prepare session handling and deprecated APIs for PHP 8.6 compatibility.

Bug Fixes:

  • Harden form output, template-set file operations, logout flows, redirect query-string handling, image processing, module manifests, and image-manager authorization against security vulnerabilities.
  • Fix search, browsing, rank lookup, failed-query handling, criteria generation, login redirects, and other production reliability issues.
  • Remove deprecated GD cleanup calls and correct related resource handling.

Enhancements:

  • Centralize path containment and safe query-string rebuilding utilities with focused tests.
  • Deprecate legacy Unicode object datatypes while preserving their current behavior.

Documentation:

  • Update release, changelog, language-difference, and BBCode release documentation for XOOPS 2.7.3.

Tests:

  • Add unit coverage for safe redirect query-string rebuilding, including malformed input, encoding, and long redirect scenarios.

Summary by CodeRabbit

  • New Features

    • Added a confirmation prompt when logging out without a valid session token.
    • Added PHP 8.5/8.6 compatibility improvements.
  • Bug Fixes

    • Restricted image processing to validated local images; remote image requests now return an error.
    • Improved redirect safety by validating and rebuilding query-string values.
    • Updated image handling to avoid deprecated functionality.
    • Added clearer logout confirmation text and safer logout handling.

A GdImage frees itself when its last reference goes away (PHP 8.0+), so
imagedestroy() has been a no-op for years and PHP 8.5 deprecates it.
Delete the calls in image.php, the image CAPTCHA renderer and Protector's
module_icon.php, unsetting where a variable or property held the image.
The CAPTCHA renderer's branch-on-destroy-failure unwinds to a plain
unset, and its required-GD-function check no longer lists the function.
The remaining occurrences are inside the vendored TCPDF, which is
upstream's to change.
With ONLY_LOCAL_IMAGES flipped to false, the raw request URL reached
getimagesize() and file_get_contents(), honoring http://, phar:// and
data:// -- an SSRF and phar-deserialization surface. The branch had also
never worked for anyone: it truncated every rooted URL to a single
character (substr($imageUrl, 0, 1) where substr($imageUrl, 1) was
meant), so no real-world behavior is lost. The constant stays and the
branch now refuses the request, so a fork that flips it fails closed
instead of fatal.
profile's user.php tore the session down on a bare GET, so any
third-party page could end a visitor's session at will (forced-logout
CSRF). A tokenless request now renders a POST confirmation carrying the
token instead of acting immediately, which keeps every existing
user.php?op=logout link working -- including the ones baked into cached
theme and block templates -- at the cost of one extra click. Adds
_US_SURETOLOGOUT to the core user language file and records it in
lang_diff.txt.
…on headers

The pm and profile preload events and profile register.php's activation
redirect appended $_SERVER['QUERY_STRING'] verbatim to their Location
targets -- eight sites. PHP already blocks CRLF in headers, so this was
never header splitting, but an attacker-shaped query string reflected
unchecked invites cache-poisoning and phishing parameter injection. The
string is now appended only when it fits a conservative character
allowlist (all core xoops_redirect producers urlencode their values, so
legitimate flows pass unchanged) and is dropped otherwise.
Copilot AI lite review requested due to automatic review settings August 24, 2026 09:28
@sourcery-ai

sourcery-ai Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Hardens image and redirect handling by removing deprecated GD cleanup APIs, disabling unsafe remote-image processing, filtering reflected query strings, and adding CSRF protection to logout while updating release metadata.

Sequence diagram for CSRF-protected logout

sequenceDiagram
    participant Browser
    participant User as user.php
    participant Security as xoopsSecurity
    participant Session as sess_handler

    Browser->>User: GET user.php?op=logout
    User->>Security: check()
    alt token missing or invalid
        User->>Browser: xoops_confirm(['op' => 'logout'], 'user.php', _US_SURETOLOGOUT)
        Browser-->>User: POST confirmation with token
        User->>Security: check()
    end
    User->>Session: regenerate_id(true)
    User-->>Browser: logout response
Loading

Sequence diagram for filtered redirect query strings

sequenceDiagram
    participant Request
    participant Preload as PmCorePreload_or_ProfileCorePreload
    participant Redirect as TargetPage

    Request->>Preload: eventCore...Start(args)
    Preload->>Preload: filteredQueryString()
    alt query string matches allowlist and is at most 512 characters
        Preload->>Redirect: header(location with filtered query string)
    else absent or invalid query string
        Preload->>Redirect: header(location without query string)
    end
Loading

Flow diagram for fail-closed image validation

flowchart TD
    A["imageFilenameCheck(imageUrl)"] --> B{Local image path?}
    B -- No --> C["exitInvalidRequest()"]
    B -- Yes --> D["getimagesize(imagePath)"]
    D --> E{Readable and within limits?}
    E -- No --> C
    E -- Yes --> F[Process local image]
Loading

File-Level Changes

Change Details Files
Remove deprecated GD resource cleanup calls in favor of PHP 8 object lifetime management.
  • Replace imagedestroy() calls with unset() for GdImage references.
  • Stop requiring imagedestroy() during GD capability checks.
  • Retain explanatory comments about PHP 8.5 deprecation behavior.
htdocs/class/captcha/image.php
htdocs/class/captcha/image/scripts/image.php
htdocs/image.php
htdocs/xoops_lib/modules/protector/module_icon.php
Close unsafe remote-image handling and enforce local-file processing.
  • Reject the non-local image branch rather than passing attacker-controlled URLs to filesystem/image APIs.
  • Update validation comments to reflect the local-only invariant.
htdocs/image.php
Sanitize query strings before reflecting them in redirect Location headers.
  • Add a bounded allowlist filter for QUERY_STRING values in PM and profile preload redirects.
  • Apply equivalent filtering to the profile registration activation redirect.
  • Drop the query string when it fails validation.
htdocs/modules/pm/preloads/core.php
htdocs/modules/profile/preloads/core.php
htdocs/modules/profile/register.php
Require CSRF validation for logout while preserving tokenless logout-link compatibility.
  • Show a confirmation form when logout lacks a valid security token.
  • Continue logout only after the token-bearing confirmation request.
  • Add the confirmation prompt language string.
htdocs/modules/profile/user.php
htdocs/language/english/user.php
Update release metadata for the hardening changes.
  • Record the changes in the versioned changelog.
  • Refresh language-difference metadata.
docs/changelog.270.txt
docs/lang_diff.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

Next included review available in 18 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: cccdf5df-9a54-4edd-ad28-e6abd98af8d1

📥 Commits

Reviewing files that changed from the base of the PR and between b180fcb and 72542d9.

📒 Files selected for processing (12)
  • docs/README.txt
  • docs/RELEASE_POST.md
  • docs/RELEASE_POST_BBCODE.txt
  • docs/changelog.270.txt
  • docs/lang_diff.txt
  • htdocs/include/file_safety.php
  • htdocs/language/english/user.php
  • htdocs/modules/pm/preloads/core.php
  • htdocs/modules/profile/preloads/core.php
  • htdocs/modules/profile/register.php
  • htdocs/modules/profile/user.php
  • tests/unit/htdocs/include/RebuildQueryStringTest.php

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2ee10c0b-813b-4a23-82b3-c1c1bcbc2f00

📥 Commits

Reviewing files that changed from the base of the PR and between d19c368 and bf27118.

📒 Files selected for processing (11)
  • docs/changelog.270.txt
  • docs/lang_diff.txt
  • htdocs/class/captcha/image.php
  • htdocs/class/captcha/image/scripts/image.php
  • htdocs/image.php
  • htdocs/language/english/user.php
  • htdocs/modules/pm/preloads/core.php
  • htdocs/modules/profile/preloads/core.php
  • htdocs/modules/profile/register.php
  • htdocs/modules/profile/user.php
  • htdocs/xoops_lib/modules/protector/module_icon.php
💤 Files with no reviewable changes (1)
  • htdocs/class/captcha/image.php

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

The release updates GD image cleanup for PHP 8.5 compatibility, disables remote image processing, validates redirect query strings, and adds session-token protection with confirmation for logout requests.

Changes

PHP compatibility and security updates

Layer / File(s) Summary
GD image lifecycle compatibility
docs/changelog.270.txt, htdocs/class/captcha/image.php, htdocs/class/captcha/image/scripts/image.php, htdocs/image.php, htdocs/xoops_lib/modules/protector/module_icon.php
GD checks no longer require imagedestroy(). CAPTCHA, core image, and Protector code now release image references with unset().
Image and redirect request validation
htdocs/image.php, htdocs/modules/pm/preloads/core.php, htdocs/modules/profile/preloads/core.php, htdocs/modules/profile/register.php
Remote image requests now return 404. PM and profile redirects append query strings only when they match the allowed character set and length limit.
Token-protected logout flow
docs/changelog.270.txt, docs/lang_diff.txt, htdocs/language/english/user.php, htdocs/modules/profile/user.php
Logout validates the session token. Invalid or missing tokens show a POST confirmation form using _US_SURETOLOGOUT.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to bf271

This change hardens image, redirect, and logout handling; no actionable merge-blocking risk remains at the current head beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant UserBrowser
  participant ProfileLogout
  participant SessionManager
  UserBrowser->>ProfileLogout: Request logout without valid token
  ProfileLogout-->>UserBrowser: Render POST confirmation form
  UserBrowser->>ProfileLogout: Submit confirmation with token
  ProfileLogout->>SessionManager: Validate session token
  SessionManager-->>ProfileLogout: Return validation result
  ProfileLogout-->>UserBrowser: Complete logout and redirect
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the pull request as a hardening change covering image handling, redirects, and logout security.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 19.35484% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 20.24%. Comparing base (b4b30b3) to head (72542d9).
⚠️ Report is 9 commits behind head on master.

Files with missing lines Patch % Lines
htdocs/modules/profile/preloads/core.php 0.00% 7 Missing ⚠️
htdocs/modules/profile/user.php 0.00% 7 Missing ⚠️
htdocs/modules/pm/preloads/core.php 0.00% 6 Missing ⚠️
htdocs/class/captcha/image/scripts/image.php 0.00% 3 Missing ⚠️
htdocs/modules/profile/register.php 0.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master     #179      +/-   ##
============================================
+ Coverage     20.18%   20.24%   +0.06%     
- Complexity     8217     8233      +16     
============================================
  Files           673      674       +1     
  Lines         44242    44297      +55     
============================================
+ Hits           8930     8970      +40     
- Misses        35312    35327      +15     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. This changes the logout trust boundary by requiring a valid session token and routing tokenless requests through a confirmation flow, so a mistaken policy or check affects every logout request immediately. Reverting restores the old flow, but sessions already invalidated or users already affected by an incorrect redirect or logout behavior cannot be fully restored by the revert.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Greptile Summary

The PR hardens image and logout handling, rebuilds reflected redirect query strings safely, updates GD usage for current PHP versions, and prepares the XOOPS 2.7.3 release documentation.

  • Routes profile and private-message redirects through a shared RFC 3986 query-string rebuilder.
  • Rejects remote image sources and removes deprecated GD cleanup calls.
  • Adds token-backed logout confirmation behavior and its language string.
  • Updates release notes, changelog, and query-string unit coverage.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains within the scope of the previous redirect thread.

No blocking failure remains.

Important Files Changed

Filename Overview
htdocs/include/file_safety.php Adds the shared query-string rebuilding helper; the previously reported 1072-byte login redirect now survives the redirect hop.
htdocs/modules/profile/preloads/core.php Uses the shared helper when forwarding profile routes, including the login path involved in the previous thread.
tests/unit/htdocs/include/RebuildQueryStringTest.php Covers safe reconstruction, malformed input, the length policy, and round-trip preservation of the previously reported long redirect.
htdocs/image.php Closes remote image handling and replaces deprecated explicit GD destruction with reference cleanup.
htdocs/modules/profile/user.php Adds a confirmation step for logout requests that do not carry a valid session token.

Reviews (4): Last reviewed commit: "fix(profile): give the logout confirmati..." | Re-trigger Greptile

Comment thread htdocs/modules/profile/preloads/core.php

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens several request/redirect and image-handling paths in XOOPS 2.7.x to reduce exposure to unsafe inputs and align GD cleanup with newer PHP behavior.

Changes:

  • Require CSRF token (or a token-backed confirmation POST) before completing logout.
  • Stop reflecting raw QUERY_STRING into multiple redirect Location headers by filtering it before appending.
  • Remove/close remote-image handling in image.php and replace imagedestroy() calls with reference cleanup (unset) for PHP 8+ GD objects.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
htdocs/xoops_lib/modules/protector/module_icon.php Replace imagedestroy() with reference cleanup for generated module icons.
htdocs/modules/profile/user.php Add token check + confirmation step to prevent tokenless forced-logout CSRF.
htdocs/modules/profile/register.php Filter query string before redirecting to activation endpoint.
htdocs/modules/profile/preloads/core.php Centralize filtered query string appending for profile-related preload redirects.
htdocs/modules/pm/preloads/core.php Centralize filtered query string appending for PM preload redirects.
htdocs/language/english/user.php Add new logout confirmation localization string.
htdocs/image.php Close remote-image branch; tighten local-only assumptions; switch GD cleanup to unset.
htdocs/class/captcha/image/scripts/image.php Replace imagedestroy() calls with reference cleanup for CAPTCHA images.
htdocs/class/captcha/image.php Remove imagedestroy from required GD function checks.
docs/lang_diff.txt Document the added language define for logout confirmation.
docs/changelog.270.txt Document security hardening + GD cleanup changes for 2.7.3.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread htdocs/modules/profile/register.php Outdated
Comment on lines +36 to +38
$queryString = $_SERVER['QUERY_STRING'] ?? '';
$queryString = preg_match('/^[A-Za-z0-9_\[\]=&%;.\-]{1,512}$/', $queryString) ? ('?' . $queryString) : '';
header('location: ./activate.php' . $queryString);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b180fcb. The reflected string is now rebuilt with parse_str() + http_build_query(PHP_QUERY_RFC3986) instead of being gated by the character class, so every emitted byte is RFC 3986-safe or a valid escape by construction -- a malformed "%ZZ" or trailing "%" reaches the Location header as "%25ZZ" / "%25", and behavior no longer depends on how a cache or user agent normalizes invalid escapes.

{
$queryString = $_SERVER['QUERY_STRING'] ?? '';

return preg_match('/^[A-Za-z0-9_\[\]=&%;.\-]{1,512}$/', $queryString) ? ('?' . $queryString) : '';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b180fcb. The reflected string is now rebuilt with parse_str() + http_build_query(PHP_QUERY_RFC3986) instead of being gated by the character class, so every emitted byte is RFC 3986-safe or a valid escape by construction -- a malformed "%ZZ" or trailing "%" reaches the Location header as "%25ZZ" / "%25", and behavior no longer depends on how a cache or user agent normalizes invalid escapes.

Comment thread htdocs/modules/pm/preloads/core.php Outdated
{
$queryString = $_SERVER['QUERY_STRING'] ?? '';

return preg_match('/^[A-Za-z0-9_\[\]=&%;.\-]{1,512}$/', $queryString) ? ('?' . $queryString) : '';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b180fcb. The reflected string is now rebuilt with parse_str() + http_build_query(PHP_QUERY_RFC3986) instead of being gated by the character class, so every emitted byte is RFC 3986-safe or a valid escape by construction -- a malformed "%ZZ" or trailing "%" reaches the Location header as "%25ZZ" / "%25", and behavior no longer depends on how a cache or user agent normalizes invalid escapes.

Follow-up to the XOOPS#179 review: the character allowlist reflected
malformed percent-escapes verbatim ("%ZZ" and a trailing "%" match the
class -- Copilot, three sites) and its 512-byte cap dropped a long
urlencoded xoops_redirect entirely, so a login lost its destination
(Greptile P1, reproduced with a 1072-byte publisher search URL). The
filter now parses the query string with parse_str() and re-emits it
with http_build_query(PHP_QUERY_RFC3986), capped at 2000 bytes for
header-size sanity: every reflected byte is RFC 3986-safe or a valid
escape by construction, hostile input becomes inert encoding instead of
costing the visitor their query, and the redirect target parses the
rebuilt string identically to the original, since parse_str() mirrors
PHP's own request parsing (round-trip equivalence probed by execution).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Comment on lines +49 to +58
private static function filteredQueryString()
{
$queryString = $_SERVER['QUERY_STRING'] ?? '';
if ('' === $queryString || strlen($queryString) > 2000) {
return '';
}
parse_str($queryString, $params);
$rebuilt = http_build_query($params, '', '&', PHP_QUERY_RFC3986);

return ('' === $rebuilt) ? '' : ('?' . $rebuilt);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4524341. The rebuild now lives once as xoops_rebuildQueryString() in include/file_safety.php -- the side-effect-free helper home that already hosts the redirect-safety functions, guarded with function_exists() like its neighbors -- and both preloads plus register.php delegate to it. The contract is pinned by tests/unit/htdocs/include/RebuildQueryStringTest.php: malformed escapes re-emitted as valid ones, every emitted byte RFC 3986-safe or a valid escape, unusable input dropped to '', and the long urlencoded xoops_redirect surviving with round-trip parse equivalence at the target.

Comment thread htdocs/modules/pm/preloads/core.php Outdated
Comment on lines +47 to +56
private static function filteredQueryString()
{
$queryString = $_SERVER['QUERY_STRING'] ?? '';
if ('' === $queryString || strlen($queryString) > 2000) {
return '';
}
parse_str($queryString, $params);
$rebuilt = http_build_query($params, '', '&', PHP_QUERY_RFC3986);

return ('' === $rebuilt) ? '' : ('?' . $rebuilt);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4524341. The rebuild now lives once as xoops_rebuildQueryString() in include/file_safety.php -- the side-effect-free helper home that already hosts the redirect-safety functions, guarded with function_exists() like its neighbors -- and both preloads plus register.php delegate to it. The contract is pinned by tests/unit/htdocs/include/RebuildQueryStringTest.php: malformed escapes re-emitted as valid ones, every emitted byte RFC 3986-safe or a valid escape, unusable input dropped to '', and the long urlencoded xoops_redirect surviving with round-trip parse equivalence at the target.

… test

Review follow-up on XOOPS#179: the parse_str()/http_build_query() rebuild was
duplicated verbatim in the pm and profile preloads and inline in
register.php. It now lives once as xoops_rebuildQueryString() in
include/file_safety.php -- the side-effect-free helper home already
hosting the redirect-safety functions, guarded with function_exists()
like its neighbors -- and all three call sites delegate to it. A new
RebuildQueryStringTest pins the contract: malformed escapes re-emitted
as valid ones, hostile input reduced to inert encoding, every emitted
byte RFC 3986-safe or a valid escape, unusable input dropped to '', and
the >512-byte urlencoded xoops_redirect surviving with round-trip
parse equivalence at the target.
Move this branch's four changelog entries out of the RC 1 block into a
new "2.7.3 Final" section, each now attributed to XOOPS#179. Split
lang_diff.txt the same way: the SCEditor strings stay under 2.7.3-RC1
and the new _US_SURETOLOGOUT confirmation string gets its own
2.7.3-Final section, so translators see exactly what changed since the
RC packs. Rewrite docs/README.txt and docs/RELEASE_POST.md from their
2.7.2 versions for 2.7.3 Final: security hardening and PHP 8.6
readiness as the headline, SCEditor and the file-based debug
configuration as features, the xoops.org-proven reliability fixes,
upgrade guidance (schema changes -> run the upgrade wizard; no
mainfile.php changes needed), and thanks to CHCCD for testing the
release candidates and reporting XOOPS#161, XOOPS#162 and XOOPS#163.
The v2.7.3-RC1 tag points at 5a7ae41 (2026-08-11); everything merged
after it belongs to Final. Verified mechanically by diffing the
changelog against its tag-time content: the seven post-tag entries --
is_long(), the constructor bare-returns and their repository-wide test
pin, the XOOPS#169 session save-handler contract, the tplsets hardening
(XOOPS#176), PathGuard (XOOPS#178), and the PR template (XOOPS#175) -- move from the
RC 1 block into the Final block, and the RC 1 block's emptied Build
section is removed. The same diff exposed two gaps, both fixed: the
Database section header (dropped by an earlier edit, leaving the XOOPS#154
Criteria entries under Security) is restored, and the post-tag fixes
that never had entries get them under Final -- the search show-all
repair and request validation and the browse.php Cache-Control fix
(reported by CHCCD in XOOPS#161, XOOPS#162, XOOPS#163) and the XOBJ_DTYPE_UNICODE_*
deprecation notice. RELEASE_POST.md gains the matching Deprecations
note, and RELEASE_POST_BBCODE.txt is the same announcement in XOOPS
BBCode, ready for the xoops.org news post.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Comment on lines +337 to +340
parse_str($queryString, $params);
$rebuilt = http_build_query($params, '', '&', PHP_QUERY_RFC3986);

return ('' === $rebuilt) ? '' : ('?' . $rebuilt);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refuted by execution (PHP 8.2.33 and 8.4.21): parse_str() discards parameters with an empty name entirely, so "=" and "=x" parse to [] and xoops_rebuildQueryString() returns '' -- the "?=" output is not reachable. This exact case is pinned in RebuildQueryStringTest ('bare equals' in the droppedStrings provider), which passes. The near miss is "%3D", which parse_str() decodes to a parameter NAMED "=" with an empty value; that re-emits as "?%3D=", which is well-formed and harmless. No change made.

Comment on lines +103 to +107
include $GLOBALS['xoops']->path('header.php');
include __DIR__ . '/header.php';
xoops_confirm(['op' => 'logout'], 'user.php', _US_SURETOLOGOUT);
include __DIR__ . '/footer.php';
exit();
Review follow-up on XOOPS#179: xoops_loadLanguage() falls back to English
only when a language pack's file is missing entirely, so a pack that
predates 2.7.3 Final has a user.php without _US_SURETOLOGOUT -- and an
undefined constant is a fatal Error on PHP 8, taking down the logout
confirmation on every non-English site until translations catch up.
Guard with defined() || define() at the use site, per the repo's
bare-conditional-constants pattern, so such sites get the English
string instead of a fatal.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

@mambax7
mambax7 merged commit 00fff34 into XOOPS:master Aug 24, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants