From b27382d0110b98782f4152fcd35c8eaf343e80e8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 3 Oct 2017 14:49:39 +1000 Subject: [PATCH] [processing] Fix history doesn't correctly escape values Fixes #17229 --- python/core/processing/qgsprocessingutils.sip | 6 +++++ .../processing/qgsprocessingparameters.cpp | 18 ++++++++------- src/core/processing/qgsprocessingutils.cpp | 9 ++++++++ src/core/processing/qgsprocessingutils.h | 5 +++++ tests/src/core/testqgsprocessing.cpp | 22 +++++++++++++++++++ 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/python/core/processing/qgsprocessingutils.sip b/python/core/processing/qgsprocessingutils.sip index f497c65b9c5..a92ff8803b0 100644 --- a/python/core/processing/qgsprocessingutils.sip +++ b/python/core/processing/qgsprocessingutils.sip @@ -100,6 +100,12 @@ class QgsProcessingUtils :rtype: str %End + static QString stringToPythonLiteral( const QString &string ); +%Docstring + Converts a string to a Python string literal. E.g. by replacing ' with \'. + :rtype: str +%End + static void createFeatureSinkPython( QgsFeatureSink **sink /Out,TransferBack/, diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index 8d3133d9f80..3442a649fc1 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -1202,7 +1202,7 @@ QString QgsProcessingParameterDefinition::valueAsPythonString( const QVariant &v if ( value.canConvert() ) return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() ); - return value.toString().prepend( '\'' ).append( '\'' ); + return QgsProcessingUtils::stringToPythonLiteral( value.toString() ); } QString QgsProcessingParameterDefinition::asScriptCode() const @@ -1304,7 +1304,7 @@ QString QgsProcessingParameterCrs::valueAsPythonString( const QVariant &value, Q p.insert( name(), value ); QgsMapLayer *layer = QgsProcessingParameters::parameterAsLayer( this, p, context ); if ( layer ) - return QgsProcessingUtils::normalizeLayerSource( layer->source() ).prepend( '\'' ).append( '\'' ); + return QgsProcessingUtils::stringToPythonLiteral( QgsProcessingUtils::normalizeLayerSource( layer->source() ) ); return QgsProcessingParameterDefinition::valueAsPythonString( value, context ); } @@ -1364,7 +1364,8 @@ QString QgsProcessingParameterMapLayer::valueAsPythonString( const QVariant &val QVariantMap p; p.insert( name(), val ); QgsMapLayer *layer = QgsProcessingParameters::parameterAsLayer( this, p, context ); - return layer ? QgsProcessingUtils::normalizeLayerSource( layer->source() ).prepend( '\'' ).append( '\'' ) : QString(); + return layer ? QgsProcessingUtils::stringToPythonLiteral( QgsProcessingUtils::normalizeLayerSource( layer->source() ) ) + : QString(); } QgsProcessingParameterMapLayer *QgsProcessingParameterMapLayer::fromScriptCode( const QString &name, const QString &description, bool isOptional, const QString &definition ) @@ -1833,7 +1834,7 @@ QString QgsProcessingParameterMultipleLayers::valueAsPythonString( const QVarian QStringList parts; Q_FOREACH ( const QgsMapLayer *layer, list ) { - parts << QgsProcessingUtils::normalizeLayerSource( layer->source() ).prepend( '\'' ).append( '\'' ); + parts << QgsProcessingUtils::stringToPythonLiteral( QgsProcessingUtils::normalizeLayerSource( layer->source() ) ); } return parts.join( ',' ).prepend( '[' ).append( ']' ); } @@ -2551,7 +2552,8 @@ QString QgsProcessingParameterVectorLayer::valueAsPythonString( const QVariant & QVariantMap p; p.insert( name(), val ); QgsVectorLayer *layer = QgsProcessingParameters::parameterAsVectorLayer( this, p, context ); - return layer ? QgsProcessingUtils::normalizeLayerSource( layer->source() ).prepend( '\'' ).append( '\'' ) : QString(); + return layer ? QgsProcessingUtils::stringToPythonLiteral( QgsProcessingUtils::normalizeLayerSource( layer->source() ) ) + : QString(); } QList QgsProcessingParameterLimitedDataTypes::dataTypes() const @@ -2652,7 +2654,7 @@ QString QgsProcessingParameterField::valueAsPythonString( const QVariant &value, QStringList parts; Q_FOREACH ( const QVariant &val, value.toList() ) { - parts << val.toString().prepend( '\'' ).append( '\'' ); + parts << QgsProcessingUtils::stringToPythonLiteral( val.toString() ); } return parts.join( ',' ).prepend( '[' ).append( ']' ); } @@ -2661,12 +2663,12 @@ QString QgsProcessingParameterField::valueAsPythonString( const QVariant &value, QStringList parts; Q_FOREACH ( QString s, value.toStringList() ) { - parts << s.prepend( '\'' ).append( '\'' ); + parts << QgsProcessingUtils::stringToPythonLiteral( s ); } return parts.join( ',' ).prepend( '[' ).append( ']' ); } - return value.toString().prepend( '\'' ).append( '\'' ); + return QgsProcessingUtils::stringToPythonLiteral( value.toString() ); } QString QgsProcessingParameterField::asScriptCode() const diff --git a/src/core/processing/qgsprocessingutils.cpp b/src/core/processing/qgsprocessingutils.cpp index e211728643d..961dbc626a4 100644 --- a/src/core/processing/qgsprocessingutils.cpp +++ b/src/core/processing/qgsprocessingutils.cpp @@ -286,6 +286,15 @@ QString QgsProcessingUtils::normalizeLayerSource( const QString &source ) return normalized.trimmed(); } +QString QgsProcessingUtils::stringToPythonLiteral( const QString &string ) +{ + QString s = string; + s.replace( '"', QStringLiteral( "\\\"" ) ); + s.replace( '\'', QStringLiteral( "\\\'" ) ); + s = s.prepend( '\'' ).append( '\'' ); + return s; +} + void parseDestinationString( QString &destination, QString &providerKey, QString &uri, QString &format, QMap &options ) { QRegularExpression splitRx( QStringLiteral( "^(.{3,}):(.*)$" ) ); diff --git a/src/core/processing/qgsprocessingutils.h b/src/core/processing/qgsprocessingutils.h index 4745a1ca978..e1ed6674529 100644 --- a/src/core/processing/qgsprocessingutils.h +++ b/src/core/processing/qgsprocessingutils.h @@ -115,6 +115,11 @@ class CORE_EXPORT QgsProcessingUtils */ static QString normalizeLayerSource( const QString &source ); + /** + * Converts a string to a Python string literal. E.g. by replacing ' with \'. + */ + static QString stringToPythonLiteral( const QString &string ); + /** * Creates a feature sink ready for adding features. The \a destination specifies a destination * URI for the resultant layer. It may be updated in place to reflect the actual destination diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 148585269f3..2573497c9c2 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -353,6 +353,7 @@ class TestQgsProcessing: public QObject void convertCompatible(); void create(); void combineFields(); + void stringToPythonLiteral(); private: @@ -1670,6 +1671,7 @@ void TestQgsProcessing::parameterCrs() QCOMPARE( def->valueAsPythonString( raster1, context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( r1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "tenbytenraster.asc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterCrs fromMap( "x" ); @@ -2007,6 +2009,7 @@ void TestQgsProcessing::parameterExtent() QCOMPARE( def->valueAsPythonString( QgsRectangle( 11, 12, 13, 14 ), context ), QStringLiteral( "'11, 13, 12, 14'" ) ); QCOMPARE( def->valueAsPythonString( QgsReferencedRectangle( QgsRectangle( 11, 12, 13, 14 ), QgsCoordinateReferenceSystem( "epsg:4326" ) ), context ), QStringLiteral( "'11, 13, 12, 14 [EPSG:4326]'" ) ); QCOMPARE( def->valueAsPythonString( "1,2,3,4 [EPSG:4326]", context ), QStringLiteral( "'1,2,3,4 [EPSG:4326]'" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=extent 1,2,3,4" ) ); @@ -2204,6 +2207,7 @@ void TestQgsProcessing::parameterFile() QCOMPARE( def->valueAsPythonString( "bricks.bmp", context ), QStringLiteral( "'bricks.bmp'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=file abc.bmp" ) ); @@ -2455,6 +2459,7 @@ void TestQgsProcessing::parameterLayerList() QCOMPARE( def->valueAsPythonString( r1->id(), context ), QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc']" ) ); QCOMPARE( def->valueAsPythonString( QStringList() << r1->id() << raster2, context ), QStringLiteral( "['" ) + testDataDir + QStringLiteral( "tenbytenraster.asc','" ) + testDataDir + QStringLiteral( "landsat.tif']" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=multiple vector" ) ); @@ -3090,6 +3095,7 @@ void TestQgsProcessing::parameterString() QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc\ndef" ), context ), QStringLiteral( "'abc\\ndef'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=string" ) ); @@ -3292,6 +3298,8 @@ void TestQgsProcessing::parameterField() QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "probably\'invalid\"field", context ), QStringLiteral( "'probably\\'invalid\\\"field'" ) ); + QString code = def->asScriptCode(); QCOMPARE( code, QStringLiteral( "##non_optional=field" ) ); @@ -3625,6 +3633,7 @@ void TestQgsProcessing::parameterFeatureSource() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( v2 ), context ), QStringLiteral( "'%1'" ).arg( vector2 ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFeatureSource fromMap( "x" ); @@ -3719,6 +3728,7 @@ void TestQgsProcessing::parameterFeatureSink() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "shp" ) ); QCOMPARE( def->generateTemporaryDestination(), QStringLiteral( "memory:" ) ); @@ -3831,6 +3841,7 @@ void TestQgsProcessing::parameterVectorOut() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QCOMPARE( def->defaultFileExtension(), QStringLiteral( "shp" ) ); QVERIFY( def->generateTemporaryDestination().endsWith( QStringLiteral( ".shp" ) ) ); @@ -3942,6 +3953,7 @@ void TestQgsProcessing::parameterRasterOut() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterRasterDestination fromMap( "x" ); @@ -4060,6 +4072,7 @@ void TestQgsProcessing::parameterFileOut() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\'complex\' username=\"complex\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFileDestination fromMap( "x" ); @@ -4133,6 +4146,7 @@ void TestQgsProcessing::parameterFolderOut() QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( "uri='complex' username=\"complex\"", context ), QStringLiteral( "'uri=\\'complex\\' username=\\\"complex\\\"'" ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFolderDestination fromMap( "x" ); @@ -5587,5 +5601,13 @@ void TestQgsProcessing::combineFields() QCOMPARE( res.at( 3 ).name(), QStringLiteral( "new_2" ) ); } +void TestQgsProcessing::stringToPythonLiteral() +{ + QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a" ) ), QStringLiteral( "'a'" ) ); + QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QString() ), QStringLiteral( "''" ) ); + QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a 'string'" ) ), QStringLiteral( "'a \\'string\\''" ) ); + QCOMPARE( QgsProcessingUtils::stringToPythonLiteral( QStringLiteral( "a \"string\"" ) ), QStringLiteral( "'a \\\"string\\\"'" ) ); +} + QGSTEST_MAIN( TestQgsProcessing ) #include "testqgsprocessing.moc"