Skip to content

[3.0] Widens the report comment column to fit an entity encoded report - #9616

Open
albertlast wants to merge 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/report-comment-column-width
Open

[3.0] Widens the report comment column to fit an entity encoded report#9616
albertlast wants to merge 1 commit into
SimpleMachines:release-3.0from
albertlast:3.0/report-comment-column-width

Conversation

@albertlast

Copy link
Copy Markdown
Collaborator

Description

Reported on the community forum: a member of a Swedish forum wrote a longish
comment when reporting a post and got

Data too long for column 'comment' at row 1
Fil: /var/www/.../forum/Sources/ReportToMod.php
Rad: 310

Line 310 in 2.1.7 is the insert into log_reported_comments. The same defect is
in 3.0, at ReportToMod::reportMsg() and ReportToMod::reportMember().

What goes wrong

The report form allows 254 characters, and the check that enforces that is

if (Utils::entityStrlen(Utils::htmlspecialchars($this->comment)) > 254) {

entityStrlen() is grapheme_strlen(entityDecode($string)), so it counts the
characters the member typed. That is the right thing for a form limit, and the
round trip through htmlspecialchars() is deliberate: it stops someone who
literally types … from having those eight characters counted as one.

But what gets stored is the entity encoded string, and every character that
needs an entity grows. A double quote becomes ", six characters. An
ampersand becomes &, five. An angle bracket becomes <, four. The
column was varchar(255), one character wider than the form limit, so there was
no room for any of that expansion at all.

Measured against the real Utils methods:

comment check sees actually stored
254 plain characters 254 254
254 characters, of which 12 are " 254 260
254 characters ending in a link with 9 & 254 290
254 × " 254 1524

So it does not take anything exotic. A report that quotes the post it is about,
or pastes the link to it, is enough.

Both engines reject it rather than truncating, and both are reachable in a
default install — MySQL 8.4 ships STRICT_TRANS_TABLES in sql_mode, and SMF
only calls setSqlMode('strict') during an upgrade, so normal requests inherit
the server default:

MySQL:      ERROR 1406 (22001): Data too long for column 'comment' at row 1
PostgreSQL: ERROR: value too long for type character varying(255)

It also leaves a mess behind. reportMsg() inserts the log_reported row
first and that commits, then the comment insert fatals. The moderators are left
with a report that has no comment attached and no MsgReport_Notify task
queued, so nobody is told about it.

On a server with strict mode off you get the other failure instead: the comment
is silently cut at 255 characters, in the middle of an entity, leaving a tail
like &qu.

The fix

Make log_reported_comments.comment a text column, so it holds the encoded
form of anything the form's own length check lets through. The 254 character
limit the member sees is unchanged, and it now means what it says.

The check in ReportToMod is left alone. It is correct as a statement of the
form limit; it was only ever wrong to size the column to it.

Nothing reads the column in a way a text type changes — there is no index on
it, and the four queries that select it (ReportedContent, RepairBoards) are
plain selects with no DISTINCT, GROUP BY or ORDER BY on the column.

Verification

Done in the Docker environment on both engines, since this is a schema change.

  • Migration on a varchar(255) column: isCandidate() true, execute() alters
    it to text with the default dropped. MySQL and PostgreSQL both.
  • Run again afterwards: isCandidate() false, so the upgrader skips it. Forcing
    execute() a second time is harmless and leaves the column as it is. MySQL and
    PostgreSQL both.
  • Fresh install path: building the table from the same definition succeeds on
    both, so the text column is valid in a CREATE TABLE as well as an
    ALTER TABLE. (A text column takes no default on MySQL, hence the
    drop_default in the migration.)
  • The comment that could not be saved before — 254 double quotes, 1524
    characters once encoded — now inserts through SMF's own Db::$db->insert()
    with the column list ReportToMod uses, and reads back byte for byte on both
    engines.
  • composer lint and vendor/bin/phpunit (280 tests, 495 assertions) pass.

On tests

No unit test accompanies this. The change is not reachable from the suite:
Schema\Column::__construct() calls Db::$db->calculate_type(), so the schema
classes cannot be instantiated without a database connection, and the length
check on the other side of the bug lives in ReportToMod::submit(), which needs
a session and a database too. Faking either is not worth it. The verification
above was done against real MySQL and PostgreSQL instead.

Note on the Swedish forum it was reported from

Worth saying, since it is the obvious first guess: å, ä and ö are not the
cause. varchar(255) counts characters and not bytes on both engines — 254 å
store fine as 254 characters and 508 bytes — and the constructor normalises to
NFC, so a decomposed a + combining ring from an Apple keyboard is composed
before anything measures it. Swedish typographic quotes (, U+201D) are not
entity encoded either. It is the ASCII ", & and < that cost.

Issues References (Fixes|Related|Closes)

  1. Reported at https://www.simplemachines.org/community/index.php?topic=594970.0

🤖 Generated with Claude Code

Reporting a post stores the comment with HTML entities in place of the
characters that need them, so a double quote costs six characters, an
ampersand five and an angle bracket four. The form allows 254 characters
and the column held 255, which leaves no room at all for that: a report
that quotes the post it is about, or pastes a link with a query string,
overflows the column and the insert fails with "Data too long for column
'comment'" on MySQL, or "value too long for type character varying(255)"
on PostgreSQL. The report row is written before the comment is, so the
failure also leaves a report behind with no comment on it and no
notification sent.

Makes the column a text column, which holds the encoded form of anything
the form's own length check lets through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant