Skip to content

Commit

Permalink
OGC parameter names are case-insensitive
Browse files Browse the repository at this point in the history
(cherry picked from commit 669f6bb)
  • Loading branch information
jef-n committed Nov 11, 2018
1 parent 20a0053 commit a50f76f
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 58 deletions.
2 changes: 1 addition & 1 deletion debian/control.in
Expand Up @@ -30,7 +30,7 @@ Build-Depends:
pyqt5-dev-tools, pyqt5-dev, pyqt5.qsci-dev,
python3-pyqt5, python3-pyqt5.qsci, python3-pyqt5.qtsql, python3-pyqt5.qtsvg,
python3-gdal,
python3-nose2, python3-yaml, python3-mock, python3-psycopg2, python3-future, python3-termcolor,
python3-nose2, python3-yaml, python3-mock, python3-psycopg2, python3-future, python3-termcolor, python3-owslib,
pkg-config,
git,
txt2tags,
Expand Down
31 changes: 20 additions & 11 deletions src/providers/wfs/qgswfsdatasourceuri.cpp
Expand Up @@ -28,6 +28,21 @@ QgsWFSDataSourceURI::QgsWFSDataSourceURI( const QString &uri )
// http://example.com/?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&TYPENAME=x&SRSNAME=y&username=foo&password=
if ( !mURI.hasParam( QgsWFSConstants::URI_PARAM_URL ) )
{
static QSet<QString> sFilter
{
QStringLiteral( "service" ),
QgsWFSConstants::URI_PARAM_VERSION,
QgsWFSConstants::URI_PARAM_TYPENAME,
QStringLiteral( "request" ),
QgsWFSConstants::URI_PARAM_BBOX,
QgsWFSConstants::URI_PARAM_SRSNAME,
QgsWFSConstants::URI_PARAM_FILTER,
QgsWFSConstants::URI_PARAM_OUTPUTFORMAT,
QgsWFSConstants::URI_PARAM_USERNAME,
QgsWFSConstants::URI_PARAM_PASSWORD,
QgsWFSConstants::URI_PARAM_AUTHCFG
};

QUrl url( uri );
// Transform all param keys to lowercase
QList<queryItem> items( url.queryItems() );
Expand Down Expand Up @@ -58,17 +73,11 @@ QgsWFSDataSourceURI::QgsWFSDataSourceURI( const QString &uri )
}

// Now remove all stuff that is not the core URL
url.removeQueryItem( QStringLiteral( "service" ) );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_VERSION );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_TYPENAME );
url.removeQueryItem( QStringLiteral( "request" ) );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_BBOX );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_SRSNAME );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_FILTER );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_OUTPUTFORMAT );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_USERNAME );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_PASSWORD );
url.removeQueryItem( QgsWFSConstants::URI_PARAM_AUTHCFG );
for ( auto param : url.queryItems() )
{
if ( sFilter.contains( param.first.toLower() ) )
url.removeAllQueryItems( param.first );
}

mURI = QgsDataSourceUri();
mURI.setParam( QgsWFSConstants::URI_PARAM_URL, url.toEncoded() );
Expand Down
17 changes: 13 additions & 4 deletions src/server/services/wcs/qgswcsutils.cpp
Expand Up @@ -233,6 +233,14 @@ namespace QgsWcs

QString serviceUrl( const QgsServerRequest &request, const QgsProject *project )
{
static QSet< QString > sFilter
{
QStringLiteral( "REQUEST" ),
QStringLiteral( "VERSION" ),
QStringLiteral( "SERVICE" ),
QStringLiteral( "_DC" )
};

QString href;
if ( project )
{
Expand All @@ -245,10 +253,11 @@ namespace QgsWcs
QUrl url = request.url();
QUrlQuery q( url );

q.removeAllQueryItems( QStringLiteral( "REQUEST" ) );
q.removeAllQueryItems( QStringLiteral( "VERSION" ) );
q.removeAllQueryItems( QStringLiteral( "SERVICE" ) );
q.removeAllQueryItems( QStringLiteral( "_DC" ) );
for ( auto param : q.queryItems() )
{
if ( sFilter.contains( param.first.toUpper() ) )
q.removeAllQueryItems( param.first );
}

url.setQuery( q );
href = url.toString();
Expand Down
50 changes: 26 additions & 24 deletions src/server/services/wfs/qgswfsgetfeature.cpp
Expand Up @@ -946,6 +946,22 @@ namespace QgsWfs

namespace
{
static QSet< QString > sParamFilter
{
QStringLiteral( "REQUEST" ),
QStringLiteral( "FORMAT" ),
QStringLiteral( "OUTPUTFORMAT" ),
QStringLiteral( "BBOX" ),
QStringLiteral( "FEATUREID" ),
QStringLiteral( "TYPENAME" ),
QStringLiteral( "FILTER" ),
QStringLiteral( "EXP_FILTER" ),
QStringLiteral( "MAXFEATURES" ),
QStringLiteral( "STARTINDEX" ),
QStringLiteral( "PROPERTYNAME" ),
QStringLiteral( "_DC" )
};


void hitGetFeature( const QgsServerRequest &request, QgsServerResponse &response, const QgsProject *project, QgsWfsParameters::Format format,
int numberOfFeatures, const QStringList &typeNames )
Expand Down Expand Up @@ -983,18 +999,11 @@ namespace QgsWfs
else
query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );

query.removeAllQueryItems( QStringLiteral( "REQUEST" ) );
query.removeAllQueryItems( QStringLiteral( "FORMAT" ) );
query.removeAllQueryItems( QStringLiteral( "OUTPUTFORMAT" ) );
query.removeAllQueryItems( QStringLiteral( "BBOX" ) );
query.removeAllQueryItems( QStringLiteral( "FEATUREID" ) );
query.removeAllQueryItems( QStringLiteral( "TYPENAME" ) );
query.removeAllQueryItems( QStringLiteral( "FILTER" ) );
query.removeAllQueryItems( QStringLiteral( "EXP_FILTER" ) );
query.removeAllQueryItems( QStringLiteral( "MAXFEATURES" ) );
query.removeAllQueryItems( QStringLiteral( "STARTINDEX" ) );
query.removeAllQueryItems( QStringLiteral( "PROPERTYNAME" ) );
query.removeAllQueryItems( QStringLiteral( "_DC" ) );
for ( auto param : query.queryItems() )
{
if ( sParamFilter.contains( param.first.toUpper() ) )
query.removeAllQueryItems( param.first );
}

query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
Expand Down Expand Up @@ -1092,18 +1101,11 @@ namespace QgsWfs
else
query.addQueryItem( QStringLiteral( "VERSION" ), QStringLiteral( "1.0.0" ) );

query.removeAllQueryItems( QStringLiteral( "REQUEST" ) );
query.removeAllQueryItems( QStringLiteral( "FORMAT" ) );
query.removeAllQueryItems( QStringLiteral( "OUTPUTFORMAT" ) );
query.removeAllQueryItems( QStringLiteral( "BBOX" ) );
query.removeAllQueryItems( QStringLiteral( "FEATUREID" ) );
query.removeAllQueryItems( QStringLiteral( "TYPENAME" ) );
query.removeAllQueryItems( QStringLiteral( "FILTER" ) );
query.removeAllQueryItems( QStringLiteral( "EXP_FILTER" ) );
query.removeAllQueryItems( QStringLiteral( "MAXFEATURES" ) );
query.removeAllQueryItems( QStringLiteral( "STARTINDEX" ) );
query.removeAllQueryItems( QStringLiteral( "PROPERTYNAME" ) );
query.removeAllQueryItems( QStringLiteral( "_DC" ) );
for ( auto param : query.queryItems() )
{
if ( sParamFilter.contains( param.first.toUpper() ) )
query.removeAllQueryItems( param.first );
}

query.addQueryItem( QStringLiteral( "REQUEST" ), QStringLiteral( "DescribeFeatureType" ) );
query.addQueryItem( QStringLiteral( "TYPENAME" ), typeNames.join( ',' ) );
Expand Down
22 changes: 16 additions & 6 deletions src/server/services/wms/qgswmsutils.cpp
Expand Up @@ -45,15 +45,25 @@ namespace QgsWms
// Build default url
if ( href.isEmpty() )
{
static QSet<QString> sFilter
{
QStringLiteral( "REQUEST" ),
QStringLiteral( "VERSION" ),
QStringLiteral( "SERVICE" ),
QStringLiteral( "LAYERS" ),
QStringLiteral( "STYLES" ),
QStringLiteral( "SLD_VERSION" ),
QStringLiteral( "_DC" )
};

href = request.url();
QUrlQuery q( href );

q.removeAllQueryItems( QStringLiteral( "REQUEST" ) );
q.removeAllQueryItems( QStringLiteral( "VERSION" ) );
q.removeAllQueryItems( QStringLiteral( "SERVICE" ) );
q.removeAllQueryItems( QStringLiteral( "LAYERS" ) );
q.removeAllQueryItems( QStringLiteral( "SLD_VERSION" ) );
q.removeAllQueryItems( QStringLiteral( "_DC" ) );
for ( auto param : q.queryItems() )
{
if ( sFilter.contains( param.first.toUpper() ) )
q.removeAllQueryItems( param.first );
}

href.setQuery( q );
}
Expand Down
4 changes: 2 additions & 2 deletions tests/src/providers/CMakeLists.txt
Expand Up @@ -71,9 +71,9 @@ ADD_QGIS_TEST(gdalprovidertest testqgsgdalprovider.cpp)

ADD_QGIS_TEST(ogrprovidertest testqgsogrprovider.cpp)

ADD_QGIS_TEST(wmscapabilititestest
ADD_QGIS_TEST(wmscapabilitiestest
testqgswmscapabilities.cpp)
TARGET_LINK_LIBRARIES(qgis_wmscapabilititestest wmsprovider_a)
TARGET_LINK_LIBRARIES(qgis_wmscapabilitiestest wmsprovider_a)

ADD_QGIS_TEST(wmsprovidertest
testqgswmsprovider.cpp)
Expand Down
26 changes: 16 additions & 10 deletions tests/src/python/test_qgsserver_wms.py
Expand Up @@ -27,7 +27,6 @@
import urllib.error

from qgis.testing import unittest
from qgis.PyQt.QtCore import QSize

import osgeo.gdal # NOQA

Expand All @@ -37,8 +36,9 @@
from qgis.core import QgsProject

# Strip path and content length because path may vary
RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \d+'
RE_ATTRIBUTES = b'[^>\s]+=[^>\s]+'
RE_STRIP_UNCHECKABLE = b'MAP=[^"]+|Content-Length: \\d+'
RE_STRIP_EXTENTS = b'<(north|east|south|west)Bound(Lat|Long)itude>.*</(north|east|south|west)Bound(Lat|Long)itude>|<BoundingBox .*/>'
RE_ATTRIBUTES = b'[^>\\s]+=[^>\\s]+'


class TestQgsServerWMSTestBase(QgsServerTestBase):
Expand All @@ -57,7 +57,7 @@ def wms_request(self, request, extra=None, project='test_project.qgs', version='
header, body = self._execute_request(query_string)
return (header, body, query_string)

def wms_request_compare(self, request, extra=None, reference_file=None, project='test_project.qgs', version='1.3.0'):
def wms_request_compare(self, request, extra=None, reference_file=None, project='test_project.qgs', version='1.3.0', ignoreExtent=False):
response_header, response_body, query_string = self.wms_request(request, extra, project, version)
response = response_header + response_body
reference_path = self.testdata_path + (request.lower() if not reference_file else reference_file) + '.txt'
Expand All @@ -67,6 +67,9 @@ def wms_request_compare(self, request, extra=None, reference_file=None, project=
f.close()
response = re.sub(RE_STRIP_UNCHECKABLE, b'*****', response)
expected = re.sub(RE_STRIP_UNCHECKABLE, b'*****', expected)
if ignoreExtent:
response = re.sub(RE_STRIP_EXTENTS, b'*****', response)
expected = re.sub(RE_STRIP_EXTENTS, b'*****', expected)

msg = "request %s failed.\nQuery: %s\nExpected file: %s\nResponse:\n%s" % (query_string, request, reference_path, response.decode('utf-8'))
self.assertXMLEqual(response, expected, msg=msg)
Expand Down Expand Up @@ -158,12 +161,13 @@ def test_wms_getcapabilities_without_title(self):
# according to OGC specifications tests.
self.wms_request_compare('GetCapabilities', reference_file='wms_getcapabilities_without_title', project='test_project_without_title.qgs')

def test_wms_getcapabilitie_empty_spatial_layer(self):
def test_wms_getcapabilities_empty_spatial_layer(self):
# The project contains a spatial layer without feature and the WMS
# extent is not configured in the project.
self.wms_request_compare('GetCapabilities',
reference_file='wms_getcapabilities_empty_spatial_layer',
project='test_project_empty_spatial_layer.qgz')
project='test_project_empty_spatial_layer.qgz',
ignoreExtent=True)

def test_wms_getcapabilities_versions(self):
# default version 1.3.0 when empty VERSION parameter
Expand Down Expand Up @@ -226,13 +230,14 @@ def test_wms_getcapabilities_url(self):
item_found = True
self.assertTrue(item_found)

# url passed in quesry string
# url passed in query string
# verify that GetCapabilities isn't put into the url for non-uppercase parameter names
project = os.path.join(self.testdata_path, "test_project_without_urls.qgs")
qs = "https://www.qgis-server.org?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(project),
"SERVICE": "WMS",
"VERSION": "1.3.0",
"REQUEST": "GetCapabilities",
"SeRvIcE": "WMS",
"VeRsIoN": "1.3.0",
"ReQuEsT": "GetCapabilities",
"STYLES": ""
}.items())])

Expand All @@ -242,6 +247,7 @@ def test_wms_getcapabilities_url(self):
for item in str(r).split("\\n"):
if "OnlineResource" in item:
self.assertEqual("xlink:href=\"https://www.qgis-server.org?" in item, True)
self.assertEqual("GetCapabilities" in item, False)
item_found = True
self.assertTrue(item_found)

Expand Down

0 comments on commit a50f76f

Please sign in to comment.