Basic Information
On PostgreSQL two accounts can be registered on what is the same email address, if the capitalisation differs. On MySQL the second registration is refused.
The "is this address already taken" check compares with a plain =, and neither side is normalised. Sources/Actions/Register2.php:536-546:
$request = Db::$db->query(
'SELECT id_member
FROM {db_prefix}members
WHERE email_address = {string:email_address}
OR email_address = {string:username}
LIMIT 1',
$reg_options['email'] is taken from the form and only run through filter_var(..., FILTER_VALIDATE_EMAIL) (Sources/Actions/Register2.php:483); it is never case-folded. smf_members.email_address is a plain varchar(255) (Sources/Db/Schema/v3_0/Members.php:144-150) and is stored exactly as typed (Sources/Actions/Register2.php:622).
That makes the check depend entirely on the collation of the column: case-insensitive on MySQL, case-sensitive on PostgreSQL. The database does not catch it either, because idx_email_address is a non-unique index, so uniqueness is enforced only by this query.
The domain part of an address is case-insensitive by definition, and in practice every mail provider treats the local part that way too, so both accounts receive their activation mail at the same mailbox.
Steps to reproduce
- Install SMF 3.0 on PostgreSQL.
- Register an account with the email address
user@example.com.
- Register a second account, with a different username, using
User@Example.com.
Expected result
The second registration is refused with "That email address is already in use", the same as on MySQL.
Actual result
The second registration is accepted and two accounts now exist on the same address.
Version/Git revision
3.0 Alpha 4 (16af1506e)
Database Engine
PostgreSQL
Database Version
PostgreSQL 17.10
PHP Version
8.4.24
Logs
# PostgreSQL 17.10
smf=> SELECT 'User@Example.com' = 'user@example.com';
?column?
----------
f
# MySQL 8.4.11
mysql> SELECT 'User@Example.com' = 'user@example.com';
+-----------------------------------------+
| 'User@Example.com' = 'user@example.com' |
+-----------------------------------------+
| 1 |
+-----------------------------------------+
Additional Information
The username side of registration does not have this problem, because it goes through spoofdetector_name, which is folded in PHP with Utils::casefold() and compared with a plain = (Sources/Unicode/SpoofDetector.php:334-352, Sources/Tasks/UpdateSpoofDetectorNames.php:113-118). That value is identical on both engines regardless of collation. Applying the same treatment to email addresses would settle this one, and would also let idx_email_address become a real unique index.
Related: #3214, on Db::$db->case_sensitive being hardcoded per engine rather than reflecting what the column actually does.
Basic Information
On PostgreSQL two accounts can be registered on what is the same email address, if the capitalisation differs. On MySQL the second registration is refused.
The "is this address already taken" check compares with a plain
=, and neither side is normalised.Sources/Actions/Register2.php:536-546:$reg_options['email']is taken from the form and only run throughfilter_var(..., FILTER_VALIDATE_EMAIL)(Sources/Actions/Register2.php:483); it is never case-folded.smf_members.email_addressis a plainvarchar(255)(Sources/Db/Schema/v3_0/Members.php:144-150) and is stored exactly as typed (Sources/Actions/Register2.php:622).That makes the check depend entirely on the collation of the column: case-insensitive on MySQL, case-sensitive on PostgreSQL. The database does not catch it either, because
idx_email_addressis a non-unique index, so uniqueness is enforced only by this query.The domain part of an address is case-insensitive by definition, and in practice every mail provider treats the local part that way too, so both accounts receive their activation mail at the same mailbox.
Steps to reproduce
user@example.com.User@Example.com.Expected result
The second registration is refused with "That email address is already in use", the same as on MySQL.
Actual result
The second registration is accepted and two accounts now exist on the same address.
Version/Git revision
3.0 Alpha 4 (
16af1506e)Database Engine
PostgreSQL
Database Version
PostgreSQL 17.10
PHP Version
8.4.24
Logs
Additional Information
The username side of registration does not have this problem, because it goes through
spoofdetector_name, which is folded in PHP withUtils::casefold()and compared with a plain=(Sources/Unicode/SpoofDetector.php:334-352,Sources/Tasks/UpdateSpoofDetectorNames.php:113-118). That value is identical on both engines regardless of collation. Applying the same treatment to email addresses would settle this one, and would also letidx_email_addressbecome a real unique index.Related: #3214, on
Db::$db->case_sensitivebeing hardcoded per engine rather than reflecting what the column actually does.