[multimedia/k3b/release/26.08] /: Consider word boundaries during feature parsing

Albert Astals Cid <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 877143f4dea422feebac4e57a322d75ccd8d6ab8 by Albert Astals Cid, on behalf of Ole Osterhagen.
Committed on 06/08/2026 at 21:08.
Pushed by aacid into branch 'release/26.08'.

Consider word boundaries during feature parsing

A simple substring search without taking word boundaries into account
can lead to wrong results. E.g. for xorrisofs, which can be used as a
replacement for mkisofs, the feature "xa" should not be listed, as this
command has no option -xa (only --xattr and --xattr-any).

In a few cases the search string has been adjusted:

cdda2wav
  * dev= -> dev=device

cdrecord
  * gracetime -> gracetime=#
  * cuefile= -> cuefile=name

cdrskin
  * gracetime -> gracetime=#

(cherry picked from commit ba63705a21502406b53743264982eb287783ead7)

M  +48   -26   libk3b/core/k3bdefaultexternalprograms.cpp
M  +2    -0    libk3b/core/k3bdefaultexternalprograms.h
M  +6    -0    tests/CMakeLists.txt
A  +34   -0    tests/k3bdefaultexternalprogramstest.cpp  *
A  +15   -0    tests/k3bdefaultexternalprogramstest.h  *

The files marked with a * at the end have a non valid license. Please read: https://community.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.


https://invent.kde.org/multimedia/k3b/-/commit/877143f4dea422feebac4e57a322d75ccd8d6ab8

diff --git a/libk3b/core/k3bdefaultexternalprograms.cpp b/libk3b/core/k3bdefaultexternalprograms.cpp
index f06491414..6334f9e54 100644
--- a/libk3b/core/k3bdefaultexternalprograms.cpp
+++ b/libk3b/core/k3bdefaultexternalprograms.cpp
@@ -20,6 +20,8 @@
 #include <QStringList>
 #include <QTextStream>
 
+using namespace Qt::StringLiterals;
+
 
 void K3b::addDefaultPrograms( K3b::ExternalBinManager* m )
 {
@@ -67,6 +69,26 @@ void K3b::addVcdimagerPrograms( K3b::ExternalBinManager* m )
 }
 
 
+bool K3b::containsWord( const QString& haystack, QStringView needle )
+{
+    if( needle.length() == 0 )
+        return true;
+
+    for( qsizetype index = 0;; ) {
+        index = haystack.indexOf( needle, index );
+        if( index == -1 )
+            return false;
+        if( index > 0 && !haystack[index - 1].isSpace() ) {
+            index += needle.length();
+            continue;
+        }
+        index += needle.length();
+        if( index >= haystack.length() || haystack[index].isSpace() )
+            return true;
+    }
+}
+
+
 class K3b::AbstractCdrtoolsProgram::Private
 {
 public:
@@ -188,17 +210,17 @@ void K3b::CdrecordProgram::parseFeatures( const QString& output, ExternalBin& bi
         bin.setVersion( QString(bin.version().versionString()).remove("-dvd") );
     }
 
-    if( output.contains( "gracetime" ) )
+    if( containsWord( output, u"gracetime=#"_s ) )
         bin.addFeature( "gracetime" );
-    if( output.contains( "-overburn" ) )
+    if( containsWord( output, u"-overburn"_s ) )
         bin.addFeature( "overburn" );
-    if( output.contains( "-text" ) )
+    if( containsWord( output, u"-text"_s ) )
         bin.addFeature( "cdtext" );
-    if( output.contains( "-clone" ) )
+    if( containsWord( output, u"-clone"_s ) )
         bin.addFeature( "clone" );
-    if( output.contains( "-tao" ) )
+    if( containsWord( output, u"-tao"_s ) )
         bin.addFeature( "tao" );
-    if( output.contains( "cuefile=" ) &&
+    if( containsWord( output, u"cuefile=name"_s ) &&
         ( usingCdrkit( bin ) || bin.version() > K3b::Version( 2, 1, -1, "a14") ) ) // cuefile handling was still buggy in a14
         bin.addFeature( "cuefile" );
 
@@ -207,7 +229,7 @@ void K3b::CdrecordProgram::parseFeatures( const QString& output, ExternalBin& bi
     // just double-checked and the help page is proper but there is no harm in having
     // two checks)
     // and the version check does not handle versions like 2.01-dvd properly
-    if( output.contains( "-xamix" ) ||
+    if( containsWord( output, u"-xamix"_s ) ||
         bin.version() >= K3b::Version( 2, 1, -1, "a12" ) ||
         usingCdrkit( bin ) )
         bin.addFeature( "xamix" );
@@ -252,15 +274,15 @@ void K3b::MkisofsProgram::parseFeatures( const QString& output, ExternalBin& bin
     if( usingCdrkit( bin ) )
         bin.addFeature( "genisoimage" );
 
-    if( output.contains( "-udf" ) )
+    if( containsWord( output, u"-udf"_s ) )
         bin.addFeature( "udf" );
-    if( output.contains( "-dvd-video" ) )
+    if( containsWord( output, u"-dvd-video"_s ) )
         bin.addFeature( "dvd-video" );
-    if( output.contains( "-joliet-long" ) )
+    if( containsWord( output, u"-joliet-long"_s ) )
         bin.addFeature( "joliet-long" );
-    if( output.contains( "-xa" ) )
+    if( containsWord( output, u"-xa"_s ) )
         bin.addFeature( "xa" );
-    if( output.contains( "-sectype" ) )
+    if( containsWord( output, u"-sectype"_s ) )
         bin.addFeature( "sectype" );
 
     if( bin.version() < K3b::Version( 1, 14) && !usingCdrkit( bin ) )
@@ -288,7 +310,7 @@ void K3b::ReadcdProgram::parseFeatures( const QString& output, ExternalBin& bin
     if( usingCdrkit( bin ) )
         bin.addFeature( "readom" );
 
-    if( output.contains( "-clone" ) )
+    if( containsWord( output, u"-clone"_s ) )
         bin.addFeature( "clone" );
 
     // FIXME: are these version correct?
@@ -309,15 +331,15 @@ void K3b::Cdda2wavProgram::parseFeatures( const QString& output, ExternalBin& bi
 {
     // features (we do this since the cdda2wav help says that the short
     //           options will disappear soon)
-    if( output.indexOf( "-info-only" ) )
+    if( containsWord( output, u"-info-only"_s ) )
         bin.addFeature( "info-only" ); // otherwise use the -J option
-    if( output.indexOf( "-no-infofile" ) )
+    if( containsWord( output, u"-no-infofile"_s ) )
         bin.addFeature( "no-infofile" ); // otherwise use the -H option
-    if( output.indexOf( "-gui" ) )
+    if( containsWord( output, u"-gui"_s ) )
         bin.addFeature( "gui" ); // otherwise use the -g option
-    if( output.indexOf( "-bulk" ) )
+    if( containsWord( output, u"-bulk"_s ) )
         bin.addFeature( "bulk" ); // otherwise use the -B option
-    if( output.indexOf( "dev=" ) )
+    if( containsWord( output, u"dev=device"_s ) )
         bin.addFeature( "dev" ); // otherwise use the -B option
 }
 
@@ -343,12 +365,12 @@ bool K3b::CdrdaoProgram::scanFeatures( ExternalBin& bin ) const
 
     if( fp.execute() >= 0 ) {
         QByteArray out = fp.readAll();
-        if( out.contains( "--overburn" ) )
+        if( containsWord( out, u"--overburn"_s ) )
             bin.addFeature( "overburn" );
-        if( out.contains( "--multi" ) )
+        if( containsWord( out, u"--multi"_s ) )
             bin.addFeature( "multisession" );
 
-        if( out.contains( "--buffer-under-run-protection" ) )
+        if( containsWord( out, u"--buffer-under-run-protection"_s ) )
             bin.addFeature( "disable-burnproof" );
 
         // SuSE 9.0 ships with a patched cdrdao 1.1.7 which contains an updated libschily
@@ -546,15 +568,15 @@ bool K3b::CdrskinProgram::scanFeatures(ExternalBin& bin) const
     if (fp.execute() >= 0) {
         QByteArray output = fp.readAll();
 
-        if (output.contains("gracetime"))
+        if (containsWord(output, u"gracetime=#"_s))
             bin.addFeature("gracetime");
-        if (output.contains("-overburn"))
+        if (containsWord(output, u"-overburn"_s))
             bin.addFeature("overburn");
-        if (output.contains("-text"))
+        if (containsWord(output, u"-text"_s))
             bin.addFeature("cdtext");
-        if (output.contains("-clone"))
+        if (containsWord(output, u"-clone"_s))
             bin.addFeature("clone");
-        if (output.contains("-tao"))
+        if (containsWord(output, u"-tao"_s))
             bin.addFeature("tao");
     }
 
diff --git a/libk3b/core/k3bdefaultexternalprograms.h b/libk3b/core/k3bdefaultexternalprograms.h
index 47cf06e78..c29c6c303 100644
--- a/libk3b/core/k3bdefaultexternalprograms.h
+++ b/libk3b/core/k3bdefaultexternalprograms.h
@@ -20,6 +20,8 @@ namespace K3b {
     LIBK3B_EXPORT void addTranscodePrograms( ExternalBinManager* );
     LIBK3B_EXPORT void addVcdimagerPrograms( ExternalBinManager* );
 
+    LIBK3B_EXPORT bool containsWord( const QString& haystack, QStringView needle );
+
     class LIBK3B_EXPORT AbstractCdrtoolsProgram : public SimpleExternalProgram
     {
     public:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index b88f93aa4..793eacecb 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -56,6 +56,12 @@ target_link_libraries(k3bdeviceglobalstest
     k3bdevice)
 add_test(NAME k3bdeviceglobalstest COMMAND k3bdeviceglobalstest)
 
+add_executable(k3bdefaultexternalprogramstest k3bdefaultexternalprogramstest.cpp)
+target_link_libraries(k3bdefaultexternalprogramstest
+    Qt6::Test
+    k3blib)
+add_test(NAME k3bdefaultexternalprogramstest COMMAND k3bdefaultexternalprogramstest)
+
 qt_generate_dbus_interface(${CMAKE_SOURCE_DIR}/src/k3bjobinterface.h org.k3b.Job.xml)
 qt_add_dbus_adaptor(dbus_sources ${CMAKE_CURRENT_BINARY_DIR}/org.k3b.Job.xml ${CMAKE_SOURCE_DIR}/src/k3bjobinterface.h K3b::JobInterface k3bjobinterfaceadaptor K3bJobInterfaceAdaptor)
 
diff --git a/tests/k3bdefaultexternalprogramstest.cpp b/tests/k3bdefaultexternalprogramstest.cpp
new file mode 100644
index 000000000..830412e1b
--- /dev/null
+++ b/tests/k3bdefaultexternalprogramstest.cpp
@@ -0,0 +1,34 @@
+#include "k3bdefaultexternalprogramstest.h"
+#include "k3bdefaultexternalprograms.h"
+
+#include <QTest>
+
+QTEST_APPLESS_MAIN( DefaultExternalProgramsTest )
+
+void DefaultExternalProgramsTest::testContainsWord_data()
+{
+    QTest::addColumn<QString>( "haystack" );
+    QTest::addColumn<QString>( "needle" );
+    QTest::addColumn<bool>( "isContained" );
+
+    QTest::newRow("empty")      << "abc"       << ""      << true;
+    QTest::newRow("left")       << "abc def"   << "abc"   << true;
+    QTest::newRow("right")      << "ghi\tjkl"  << "jkl"   << true;
+    QTest::newRow("middle")     << "x\ny\rz"   << "y"     << true;
+    QTest::newRow("match")      << "ab a c"    << "a"     << true;
+    QTest::newRow("no match 1") << "x"         << "a"     << false;
+    QTest::newRow("no match 2") << ""          << "a"     << false;
+    QTest::newRow("partial 1")  << "appletree" << "apple" << false;
+    QTest::newRow("partial 2")  << "appletree" << "tree"  << false;
+}
+
+void DefaultExternalProgramsTest::testContainsWord()
+{
+    QFETCH( QString, haystack );
+    QFETCH( QString, needle );
+    QFETCH( bool,    isContained );
+
+    QCOMPARE( K3b::containsWord( haystack, needle ), isContained );
+}
+
+#include "moc_k3bdefaultexternalprogramstest.cpp"
diff --git a/tests/k3bdefaultexternalprogramstest.h b/tests/k3bdefaultexternalprogramstest.h
new file mode 100644
index 000000000..44b29bd19
--- /dev/null
+++ b/tests/k3bdefaultexternalprogramstest.h
@@ -0,0 +1,15 @@
+#ifndef K3B_DEFAULT_EXTERNAL_PROGRAMS_TEST_H
+#define K3B_DEFAULT_EXTERNAL_PROGRAMS_TEST_H
+
+#include <QObject>
+
+class DefaultExternalProgramsTest : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void testContainsWord_data();
+    void testContainsWord();
+};
+
+#endif // K3B_DEFAULT_EXTERNAL_PROGRAMS_TEST_H
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.