From 71ad98d3d7f0ee7989b697c97094efdfdec91da4 Mon Sep 17 00:00:00 2001 From: Richard Kello Date: Fri, 10 Jul 2026 08:15:58 +0200 Subject: [PATCH 1/4] Align mobile regex with server regex --- core/coreutils.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/coreutils.cpp b/core/coreutils.cpp index a17b38aa8..b50940045 100644 --- a/core/coreutils.cpp +++ b/core/coreutils.cpp @@ -271,9 +271,13 @@ bool CoreUtils::isAuthConfigFile( const QString filePath ) bool CoreUtils::isValidName( const QString &name ) { - static QRegularExpression reForbiddenmNames( R"([@#$%^&*\(\)\{\}\[\]\\\/\|\+=<>~\?:;,`\'\"]|^[\s^\.].*$|^CON$|^PRN$|^AUX$|^NUL$|^COM\d$|^LPT\d|^support$|^helpdesk$|^merginmaps$|^lutraconsulting$|^mergin$|^lutra$|^input$|^sales$|^admin$)", QRegularExpression::CaseInsensitiveOption ); + static QRegularExpression reForbiddenmNames( R"(^[\s^\.].*$|^CON$|^PRN$|^AUX$|^NUL$|^COM\d$|^LPT\d|^support$|^helpdesk$|^merginmaps$|^lutraconsulting$|^mergin$|^lutra$|^input$|^sales$|^admin$)", QRegularExpression::CaseInsensitiveOption ); + static QRegularExpression reValidCharacters( R"(^[\w\s\-\.]+$)" ); + QRegularExpressionMatch matchForbiddenNames = reForbiddenmNames.match( name ); - return !matchForbiddenNames.hasMatch(); + QRegularExpressionMatch matchValidCharacters = reValidCharacters.match( name ); + + return !matchForbiddenNames.hasMatch() && matchValidCharacters.hasMatch(); } QString CoreUtils::nameAbbr( const QString &name, const QString &email ) From d1c3289e079941a21502c7c6fa11af9ddbab539c Mon Sep 17 00:00:00 2001 From: Richard Kello Date: Mon, 20 Jul 2026 16:37:51 +0200 Subject: [PATCH 2/4] Split rules to mimic server and Add file length rule --- core/coreutils.cpp | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/core/coreutils.cpp b/core/coreutils.cpp index b50940045..e5146780b 100644 --- a/core/coreutils.cpp +++ b/core/coreutils.cpp @@ -269,15 +269,36 @@ bool CoreUtils::isAuthConfigFile( const QString filePath ) return filePath == AUTH_CONFIG_FILENAME; } -bool CoreUtils::isValidName( const QString &name ) +static bool hasValidFirstCharacter( const QString &name ) { - static QRegularExpression reForbiddenmNames( R"(^[\s^\.].*$|^CON$|^PRN$|^AUX$|^NUL$|^COM\d$|^LPT\d|^support$|^helpdesk$|^merginmaps$|^lutraconsulting$|^mergin$|^lutra$|^input$|^sales$|^admin$)", QRegularExpression::CaseInsensitiveOption ); - static QRegularExpression reValidCharacters( R"(^[\w\s\-\.]+$)" ); + static QRegularExpression re( R"(^[\s^\.].*$)", QRegularExpression::CaseInsensitiveOption ); + return !re.match( name ).hasMatch(); +} - QRegularExpressionMatch matchForbiddenNames = reForbiddenmNames.match( name ); - QRegularExpressionMatch matchValidCharacters = reValidCharacters.match( name ); +static bool isInvalidFilename( const QString &name ) +{ + if ( name.length() > 255 ) + return true; - return !matchForbiddenNames.hasMatch() && matchValidCharacters.hasMatch(); + static QRegularExpression re( R"(^CON$|^PRN$|^AUX$|^NUL$|^COM\d$|^LPT\d)", QRegularExpression::CaseInsensitiveOption ); + return re.match( name ).hasMatch(); +} + +static bool isReservedWord( const QString &name ) +{ + static QRegularExpression re( R"(^support$|^helpdesk$|^merginmaps$|^lutraconsulting$|^mergin$|^lutra$|^input$|^sales$|^admin$)", QRegularExpression::CaseInsensitiveOption ); + return re.match( name ).hasMatch(); +} + +static bool hasValidCharacters( const QString &name ) +{ + static QRegularExpression re( R"(^[\w\s\-\.]+$)" ); + return re.match( name ).hasMatch(); +} + +bool CoreUtils::isValidName( const QString &name ) +{ + return hasValidFirstCharacter( name ) && !isInvalidFilename( name ) && !isReservedWord( name ) && hasValidCharacters( name ); } QString CoreUtils::nameAbbr( const QString &name, const QString &email ) From b6b08f929440cc41b1648d8203df63a075156a43 Mon Sep 17 00:00:00 2001 From: Richard Kello Date: Tue, 21 Jul 2026 11:22:06 +0200 Subject: [PATCH 3/4] Update regex flow --- core/coreutils.cpp | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/core/coreutils.cpp b/core/coreutils.cpp index e5146780b..ada9eb787 100644 --- a/core/coreutils.cpp +++ b/core/coreutils.cpp @@ -269,36 +269,29 @@ bool CoreUtils::isAuthConfigFile( const QString filePath ) return filePath == AUTH_CONFIG_FILENAME; } -static bool hasValidFirstCharacter( const QString &name ) -{ - static QRegularExpression re( R"(^[\s^\.].*$)", QRegularExpression::CaseInsensitiveOption ); - return !re.match( name ).hasMatch(); -} - -static bool isInvalidFilename( const QString &name ) +bool CoreUtils::isValidName( const QString &name ) { if ( name.length() > 255 ) - return true; + return false; - static QRegularExpression re( R"(^CON$|^PRN$|^AUX$|^NUL$|^COM\d$|^LPT\d)", QRegularExpression::CaseInsensitiveOption ); - return re.match( name ).hasMatch(); -} + // name must not start with whitespace, '^' or '.' + static QRegularExpression invalidFirstCharacterRe( R"(^[\s^\.].*$)", QRegularExpression::CaseInsensitiveOption ); + if ( invalidFirstCharacterRe.match( name ).hasMatch() ) + return false; -static bool isReservedWord( const QString &name ) -{ - static QRegularExpression re( R"(^support$|^helpdesk$|^merginmaps$|^lutraconsulting$|^mergin$|^lutra$|^input$|^sales$|^admin$)", QRegularExpression::CaseInsensitiveOption ); - return re.match( name ).hasMatch(); -} + // name must not be a reserved Windows device filename (CON, PRN, AUX, NUL, COM1-9, LPT1-9) + static QRegularExpression invalidFilenameRe( R"(^CON$|^PRN$|^AUX$|^NUL$|^COM\d$|^LPT\d)", QRegularExpression::CaseInsensitiveOption ); + if ( invalidFilenameRe.match( name ).hasMatch() ) + return false; -static bool hasValidCharacters( const QString &name ) -{ - static QRegularExpression re( R"(^[\w\s\-\.]+$)" ); - return re.match( name ).hasMatch(); -} + // name must not be a word reserved by Mergin Maps / Lutra Consulting + static QRegularExpression reservedWordRe( R"(^support$|^helpdesk$|^merginmaps$|^lutraconsulting$|^mergin$|^lutra$|^input$|^sales$|^admin$)", QRegularExpression::CaseInsensitiveOption ); + if ( reservedWordRe.match( name ).hasMatch() ) + return false; -bool CoreUtils::isValidName( const QString &name ) -{ - return hasValidFirstCharacter( name ) && !isInvalidFilename( name ) && !isReservedWord( name ) && hasValidCharacters( name ); + // name must contain only word characters, whitespace, '-' or '.' + static QRegularExpression validCharactersRe( R"(^[\w\s\-\.]+$)" ); + return validCharactersRe.match( name ).hasMatch(); } QString CoreUtils::nameAbbr( const QString &name, const QString &email ) From f9958d00fd3ff4eefd033087befe648fefffc53f Mon Sep 17 00:00:00 2001 From: Richard Kello Date: Tue, 21 Jul 2026 13:28:14 +0200 Subject: [PATCH 4/4] Update test and Update regex to work with unicode --- app/test/testcoreutils.cpp | 2 +- core/coreutils.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/test/testcoreutils.cpp b/app/test/testcoreutils.cpp index 23d6d78e7..b4d8117b4 100644 --- a/app/test/testcoreutils.cpp +++ b/app/test/testcoreutils.cpp @@ -176,7 +176,7 @@ void TestCoreUtils::testNameValidation() { QStringLiteral( "-project" ), true }, { QStringLiteral( "proj_ect" ), true }, { QStringLiteral( "proj.ect" ), true }, - { QStringLiteral( "proj!ect" ), true }, + { QStringLiteral( "proj!ect" ), false }, { QStringLiteral( " project" ), false }, { QStringLiteral( ".project" ), false }, diff --git a/core/coreutils.cpp b/core/coreutils.cpp index ada9eb787..143e1f81b 100644 --- a/core/coreutils.cpp +++ b/core/coreutils.cpp @@ -290,7 +290,7 @@ bool CoreUtils::isValidName( const QString &name ) return false; // name must contain only word characters, whitespace, '-' or '.' - static QRegularExpression validCharactersRe( R"(^[\w\s\-\.]+$)" ); + static QRegularExpression validCharactersRe( R"(^[\w\s\-\.]+$)", QRegularExpression::UseUnicodePropertiesOption ); return validCharactersRe.match( name ).hasMatch(); }