Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion resources/server/api/ogc/templates/wfs3/getFeature.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h1>{{ metadata.pageTitle }}</h1>
<dl class="row">
{% for name, value in properties %}
<dt class="col-sm-12">{{ name }}</dt>
<dd class="col-sm-12">{{ value }}</dd>
<dd class="col-sm-12">{{ escape(value) }}</dd>
{% endfor %}
</dl>
</div>
Expand Down
2 changes: 1 addition & 1 deletion resources/server/api/ogc/templates/wfs3/getFeatures.html
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ <h2><a href="{{ path_append( feature.id ) }}">{{ metadata.layerTitle }} {{ featu
<dl class="row">
{% for name, value in feature.properties %}
<dt class="col-sm-12">{{ name }}</dt>
<dd class="col-sm-12">{{ if_nullptr_null_str(value) }}</dd>
<dd class="col-sm-12">{{ escape(if_nullptr_null_str(value)) }}</dd>
{% endfor %}
</dl>
{% endfor %}
Expand Down
33 changes: 33 additions & 0 deletions src/server/qgsserverogcapihandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ void QgsServerOgcApiHandler::htmlDump( const json &data, const QgsServerApiConte
// Get the template directory and the file name
QFileInfo pathInfo { path };
Environment env { QString( pathInfo.dir().path() + QDir::separator() ).toStdString() };
// Do not call env.set_html_autoescape( true ) because that would escape links too
// use the escape() function in the templates instead

// For template debugging:
env.add_callback( "json_dump", 0, [=]( Arguments & ) {
Expand Down Expand Up @@ -437,6 +439,37 @@ void QgsServerOgcApiHandler::htmlDump( const json &data, const QgsServerApiConte
return out;
} );

// HTML escape function
env.add_callback( "escape", 1, []( Arguments &args ) {
std::string str { args.at( 0 )->get<std::string>() };
std::string escaped;
escaped.reserve( str.size() );
for ( const char c : str )
{
switch ( c )
{
case '&':
escaped.append( "&amp;" );
break;
case '<':
escaped.append( "&lt;" );
break;
case '>':
escaped.append( "&gt;" );
break;
case '"':
escaped.append( "&quot;" );
break;
case '\'':
escaped.append( "&#39;" );
break;
default:
escaped.push_back( c );
}
}
return escaped;
} );

context.response()->write( env.render_file( pathInfo.fileName().toStdString(), data ) );
}
catch ( std::exception &e )
Expand Down
39 changes: 39 additions & 0 deletions tests/src/python/test_qgsserver_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,45 @@ def test_wfs3_collections_html(self):
)
self.compareApi(request, project, "test_wfs3_collections_project.html")

def test_wfs3_getfeature_html_escape(self):

# Create mem layer with a single text field
layer = QgsVectorLayer(
"Point?crs=epsg:4326&field=txt:string", "test_layer", "memory"
)
self.assertTrue(layer.isValid())
f = QgsFeature(layer.fields())
f.setGeometry(QgsGeometry.fromWkt("POINT(1 1)"))
f.setAttribute("txt", "<b>test</b>")
layer.dataProvider().addFeatures([f])

p = QgsProject()
p.addMapLayer(layer)

# Expose to WFS
p.writeEntry("WFSLayers", "/", [layer.id()])

request = QgsBufferServerRequest(
"http://server.qgis.org/wfs3/collections/test_layer/items/1.html"
)
response = QgsBufferServerResponse()
server = QgsServer()

server.handleRequest(request, response, p)
self.assertEqual(response.statusCode(), 200)
self.assertNotIn("<b>test</b>", str(response.body(), "utf-8"))
self.assertIn("&lt;b&gt;test&lt;/b&gt;", str(response.body(), "utf-8"))

# Same test with the whole collection
request = QgsBufferServerRequest(
"http://server.qgis.org/wfs3/collections/test_layer/items.html"
)
response = QgsBufferServerResponse()
server.handleRequest(request, response, p)
self.assertEqual(response.statusCode(), 200)
self.assertNotIn("<b>test</b>", str(response.body(), "utf-8"))
self.assertIn("&lt;b&gt;test&lt;/b&gt;", str(response.body(), "utf-8"))

def test_wfs3_collections_content_type(self):
"""Test WFS3 API collections in html format with Accept header"""

Expand Down
Loading