Skip to content
Permalink
Browse files

PG style storage: replace forbidden XML unicode chars

Fixes #3800
  • Loading branch information
elpaso authored and nyalldawson committed Jul 29, 2020
1 parent 8d4843d commit 0f7a946f5184849c830ec44b2d73af037e1e9b78
@@ -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" ) )
{
@@ -659,6 +659,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;
@@ -4935,12 +4963,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 )
{
@@ -5186,6 +5220,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;
}

@@ -5333,6 +5369,8 @@ QString QgsPostgresProviderMetadata::getStyleById( const QString &uri, QString s

conn->unref();

QgsPostgresUtils::restoreInvalidXmlChars( style );

return style;
}

@@ -521,6 +521,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 );
};

/**
@@ -1911,6 +1911,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 0f7a946

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