Skip to content
Permalink
Browse files

PG style storage: replace forbidden XML unicode chars

  • Loading branch information
github-actions committed Jul 29, 2020
1 parent 1763d33 commit cdb9dd263a891d482e6b7e8ca673553143d1f0b2
@@ -159,7 +159,7 @@ QgsDataSourceUri::QgsDataSourceUri( const QString &u )
}
else if ( pname == QLatin1String( "connect_timeout" ) )
{
QgsDebugMsg( QStringLiteral( "connection timeout ignored" ) );
QgsDebugMsgLevel( QStringLiteral( "connection timeout ignored" ), 3 );
}
else if ( pname == QLatin1String( "dbname" ) )
{
@@ -707,6 +707,34 @@ QString QgsPostgresUtils::andWhereClauses( const QString &c1, const QString &c2
return QStringLiteral( "(%1) AND (%2)" ).arg( c1, c2 );
}

void QgsPostgresUtils::replaceInvalidXmlChars( QString &xml )
{
static const QRegularExpression replaceRe { QStringLiteral( "([\x00-\x08\x0B-\x1F\x7F])" ) };
QRegularExpressionMatchIterator it {replaceRe.globalMatch( xml ) };
while ( it.hasNext() )
{
const QRegularExpressionMatch match { it.next() };
const QChar c { match.captured( 1 ).at( 0 ) };
xml.replace( c, QStringLiteral( "UTF-8[%1]" ).arg( c.unicode() ) );
}
}

void QgsPostgresUtils::restoreInvalidXmlChars( QString &xml )
{
static const QRegularExpression replaceRe { QStringLiteral( R"raw(UTF-8\[(\d+)\])raw" ) };
QRegularExpressionMatchIterator it {replaceRe.globalMatch( xml ) };
while ( it.hasNext() )
{
const QRegularExpressionMatch match { it.next() };
bool ok;
const ushort code { match.captured( 1 ).toUShort( &ok ) };
if ( ok )
{
xml.replace( QStringLiteral( "UTF-8[%1]" ).arg( code ), QChar( code ) );
}
}
}

QString QgsPostgresProvider::filterWhereClause() const
{
QString where;
@@ -4985,12 +5013,18 @@ QgsVectorLayerExporter::ExportError QgsPostgresProviderMetadata::createEmptyLaye
);
}

bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &qmlStyle, const QString &sldStyle,
bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString &qmlStyleIn, const QString &sldStyleIn,
const QString &styleName, const QString &styleDescription,
const QString &uiFileContent, bool useAsDefault, QString &errCause )
{
QgsDataSourceUri dsUri( uri );

// Replace invalid XML characters
QString qmlStyle { qmlStyleIn };
QgsPostgresUtils::replaceInvalidXmlChars( qmlStyle );
QString sldStyle { sldStyleIn };
QgsPostgresUtils::replaceInvalidXmlChars( sldStyle );

QgsPostgresConn *conn = QgsPostgresConn::connectDb( dsUri.connectionInfo( false ), false );
if ( !conn )
{
@@ -5236,6 +5270,8 @@ QString QgsPostgresProviderMetadata::loadStyle( const QString &uri, QString &err
QString style = result.PQntuples() == 1 ? result.PQgetvalue( 0, 0 ) : QString();
conn->unref();

QgsPostgresUtils::restoreInvalidXmlChars( style );

return style;
}

@@ -5383,6 +5419,8 @@ QString QgsPostgresProviderMetadata::getStyleById( const QString &uri, QString s

conn->unref();

QgsPostgresUtils::restoreInvalidXmlChars( style );

return style;
}

@@ -522,6 +522,12 @@ class QgsPostgresUtils
{
return x <= ( ( INT32PK_OFFSET ) / 2.0 ) ? x : -( INT32PK_OFFSET - x );
}

//! Replaces invalid XML chars with UTF-8[<char_code>]
static void replaceInvalidXmlChars( QString &xml );

//! Replaces UTF-8[<char_code>] with the actual unicode char
static void restoreInvalidXmlChars( QString &xml );
};

/**
@@ -1719,6 +1719,35 @@ def testStyleWithGeometryType(self):
ids = styles[1]
self.assertEqual(len(ids), 0)

def testSaveStyleInvalidXML(self):

self.execSQLCommand('DROP TABLE IF EXISTS layer_styles CASCADE')

vl = self.getEditableLayer()
self.assertTrue(vl.isValid())
self.assertTrue(
vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported())
self.assertTrue(vl.dataProvider().isDeleteStyleFromDatabaseSupported())

mFilePath = QDir.toNativeSeparators(
'%s/symbol_layer/%s.qml' % (unitTestDataPath(), "fontSymbol"))
status = vl.loadNamedStyle(mFilePath)
self.assertTrue(status)

errorMsg = vl.saveStyleToDatabase(
"fontSymbol", "font with invalid utf8 char", False, "")
self.assertEqual(errorMsg, "")

qml, errmsg = vl.getStyleFromDatabase("1")
self.assertTrue('v="\u001E"' in qml)
self.assertEqual(errmsg, "")

# Test loadStyle from metadata
md = QgsProviderRegistry.instance().providerMetadata('postgres')
qml = md.loadStyle(self.dbconn + " type=POINT table=\"qgis_test\".\"editData\" (geom)", 'fontSymbol')
self.assertTrue(qml.startswith('<!DOCTYPE qgi'), qml)
self.assertTrue('v="\u001E"' in qml)

def testHasMetadata(self):
# views don't have metadata
vl = QgsVectorLayer('{} table="qgis_test"."{}" key="pk" sql='.format(self.dbconn, 'bikes_view'), "bikes_view",
@@ -0,0 +1,162 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="3.15.0-Master" minScale="100000000" simplifyDrawingHints="0" labelsEnabled="0" hasScaleBasedVisibilityFlag="0" readOnly="0" simplifyMaxScale="1" simplifyDrawingTol="1.2" maxScale="0" styleCategories="AllStyleCategories" simplifyAlgorithm="0" simplifyLocal="1">
<flags>
<Identifiable>1</Identifiable>
<Removable>1</Removable>
<Searchable>1</Searchable>
</flags>
<temporal endField="" startExpression="" durationField="" fixedDuration="0" enabled="0" accumulate="0" durationUnit="min" endExpression="" startField="" mode="0">
<fixedRange>
<start></start>
<end></end>
</fixedRange>
</temporal>
<renderer-v2 symbollevels="0" forceraster="0" type="singleSymbol" enableorderby="0">
<symbols>
<symbol name="0" alpha="1" force_rhr="0" type="marker" clip_to_extent="1">
<layer enabled="1" class="FontMarker" pass="0" locked="0">
<prop k="angle" v="0"/>
<prop k="chr" v=""/>
<prop k="color" v="145,82,45,255"/>
<prop k="font" v="Dingbats"/>
<prop k="font_style" v=""/>
<prop k="horizontal_anchor_point" v="1"/>
<prop k="joinstyle" v="bevel"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="35,35,35,255"/>
<prop k="outline_width" v="0"/>
<prop k="outline_width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="size" v="2"/>
<prop k="size_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="size_unit" v="MM"/>
<prop k="vertical_anchor_point" v="1"/>
<data_defined_properties>
<Option type="Map">
<Option value="" name="name" type="QString"/>
<Option name="properties"/>
<Option value="collection" name="type" type="QString"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale/>
</renderer-v2>
<customproperties>
<property value="0" key="embeddedWidgets/count"/>
<property key="variableNames"/>
<property key="variableValues"/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerOpacity>1</layerOpacity>
<SingleCategoryDiagramRenderer attributeLegend="1" diagramType="Histogram">
<DiagramCategory maxScaleDenominator="1e+08" backgroundColor="#ffffff" penAlpha="255" penColor="#000000" scaleBasedVisibility="0" rotationOffset="270" width="15" lineSizeType="MM" labelPlacementMethod="XHeight" sizeScale="3x:0,0,0,0,0,0" spacing="5" height="15" backgroundAlpha="255" lineSizeScale="3x:0,0,0,0,0,0" diagramOrientation="Up" spacingUnitScale="3x:0,0,0,0,0,0" minScaleDenominator="0" minimumSize="0" sizeType="MM" opacity="1" spacingUnit="MM" direction="0" enabled="0" barWidth="5" showAxis="1" scaleDependency="Area" penWidth="0">
<fontProperties description="Noto Sans,10,-1,5,50,0,0,0,0,0,Regular" style="Regular"/>
<axisSymbol>
<symbol name="" alpha="1" force_rhr="0" type="line" clip_to_extent="1">
<layer enabled="1" class="SimpleLine" pass="0" locked="0">
<prop k="align_dash_pattern" v="0"/>
<prop k="capstyle" v="square"/>
<prop k="customdash" v="5;2"/>
<prop k="customdash_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="customdash_unit" v="MM"/>
<prop k="dash_pattern_offset" v="0"/>
<prop k="dash_pattern_offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="dash_pattern_offset_unit" v="MM"/>
<prop k="draw_inside_polygon" v="0"/>
<prop k="joinstyle" v="bevel"/>
<prop k="line_color" v="35,35,35,255"/>
<prop k="line_style" v="solid"/>
<prop k="line_width" v="0.26"/>
<prop k="line_width_unit" v="MM"/>
<prop k="offset" v="0"/>
<prop k="offset_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="ring_filter" v="0"/>
<prop k="tweak_dash_pattern_on_corners" v="0"/>
<prop k="use_custom_dash" v="0"/>
<prop k="width_map_unit_scale" v="3x:0,0,0,0,0,0"/>
<data_defined_properties>
<Option type="Map">
<Option value="" name="name" type="QString"/>
<Option name="properties"/>
<Option value="collection" name="type" type="QString"/>
</Option>
</data_defined_properties>
</layer>
</symbol>
</axisSymbol>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings placement="0" priority="0" dist="0" zIndex="0" linePlacementFlags="18" showAll="1" obstacle="0">
<properties>
<Option type="Map">
<Option value="" name="name" type="QString"/>
<Option name="properties"/>
<Option value="collection" name="type" type="QString"/>
</Option>
</properties>
</DiagramLayerSettings>
<geometryOptions removeDuplicateNodes="0" geometryPrecision="0">
<activeChecks/>
<checkConfiguration/>
</geometryOptions>
<referencedLayers/>
<referencingLayers/>
<fieldConfiguration/>
<aliases/>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<defaults/>
<constraints/>
<constraintExpressions/>
<expressionfields/>
<attributeactions>
<defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/>
</attributeactions>
<attributetableconfig actionWidgetStyle="dropDown" sortOrder="0" sortExpression="">
<columns>
<column width="-1" type="actions" hidden="1"/>
</columns>
</attributetableconfig>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
<storedexpressions/>
<editform tolerant="1"></editform>
<editforminit/>
<editforminitcodesource>0</editforminitcodesource>
<editforminitfilepath></editforminitfilepath>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from qgis.PyQt.QtWidgets import QWidget

def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<editorlayout>generatedlayout</editorlayout>
<editable/>
<labelOnTop/>
<dataDefinedFieldProperties/>
<widgets/>
<previewExpression></previewExpression>
<mapTip></mapTip>
<layerGeometryType>0</layerGeometryType>
</qgis>

0 comments on commit cdb9dd2

Please sign in to comment.
You can’t perform that action at this time.