diff --git a/python/server/auto_generated/qgsserversettings.sip.in b/python/server/auto_generated/qgsserversettings.sip.in index 27b8870cb449..bae041124ae9 100644 --- a/python/server/auto_generated/qgsserversettings.sip.in +++ b/python/server/auto_generated/qgsserversettings.sip.in @@ -134,6 +134,24 @@ Show group (thousand) separator :return: if group separator must be shown, default to FALSE. +.. versionadded:: 3.8 +%End + + int wmsMaxHeight() const; +%Docstring +Returns the server-wide max height of a WMS GetMap request. The lower one of this and the project configuration is used. + +:return: the max height of a WMS GetMap request. + +.. versionadded:: 3.8 +%End + + int wmsMaxWidth() const; +%Docstring +Returns the server-wide max width of a WMS GetMap request. The lower one of this and the project configuration is used. + +:return: the max width of a WMS GetMap request. + .. versionadded:: 3.8 %End diff --git a/src/server/qgsserversettings.cpp b/src/server/qgsserversettings.cpp index b0be9feb6a4e..78524cf01dbd 100644 --- a/src/server/qgsserversettings.cpp +++ b/src/server/qgsserversettings.cpp @@ -33,8 +33,8 @@ void QgsServerSettings::initSettings() // options path const Setting sOptPath = { QgsServerSettingsEnv::QGIS_OPTIONS_PATH, QgsServerSettingsEnv::DEFAULT_VALUE, - "Override the default path for user configuration", - "", + QStringLiteral( "Override the default path for user configuration" ), + QString(), QVariant::String, QVariant( "" ), QVariant() @@ -44,8 +44,8 @@ void QgsServerSettings::initSettings() // parallel rendering const Setting sParRend = { QgsServerSettingsEnv::QGIS_SERVER_PARALLEL_RENDERING, QgsServerSettingsEnv::DEFAULT_VALUE, - "Activate/Deactivate parallel rendering for WMS getMap request", - "/qgis/parallel_rendering", + QStringLiteral( "Activate/Deactivate parallel rendering for WMS getMap request" ), + QStringLiteral( "/qgis/parallel_rendering" ), QVariant::Bool, QVariant( false ), QVariant() @@ -55,8 +55,8 @@ void QgsServerSettings::initSettings() // max threads const Setting sMaxThreads = { QgsServerSettingsEnv::QGIS_SERVER_MAX_THREADS, QgsServerSettingsEnv::DEFAULT_VALUE, - "Number of threads to use when parallel rendering is activated", - "/qgis/max_threads", + QStringLiteral( "Number of threads to use when parallel rendering is activated" ), + QStringLiteral( "/qgis/max_threads" ), QVariant::Int, QVariant( -1 ), QVariant() @@ -66,8 +66,8 @@ void QgsServerSettings::initSettings() // log level const Setting sLogLevel = { QgsServerSettingsEnv::QGIS_SERVER_LOG_LEVEL, QgsServerSettingsEnv::DEFAULT_VALUE, - "Log level", - "", + QStringLiteral( "Log level" ), + QString(), QVariant::Int, QVariant( Qgis::None ), QVariant() @@ -77,8 +77,8 @@ void QgsServerSettings::initSettings() // log file const Setting sLogFile = { QgsServerSettingsEnv::QGIS_SERVER_LOG_FILE, QgsServerSettingsEnv::DEFAULT_VALUE, - "Log file", - "", + QStringLiteral( "Log file" ), + QString(), QVariant::String, QVariant( "" ), QVariant() @@ -88,8 +88,8 @@ void QgsServerSettings::initSettings() // log to stderr const Setting sLogStderr = { QgsServerSettingsEnv::QGIS_SERVER_LOG_STDERR, QgsServerSettingsEnv::DEFAULT_VALUE, - "Activate/Deactivate logging to stderr", - "", + QStringLiteral( "Activate/Deactivate logging to stderr" ), + QString(), QVariant::Bool, QVariant( false ), QVariant() @@ -99,8 +99,8 @@ void QgsServerSettings::initSettings() // project file const Setting sProject = { QgsServerSettingsEnv::QGIS_PROJECT_FILE, QgsServerSettingsEnv::DEFAULT_VALUE, - "QGIS project file", - "", + QStringLiteral( "QGIS project file" ), + QString(), QVariant::String, QVariant( "" ), QVariant() @@ -110,8 +110,8 @@ void QgsServerSettings::initSettings() // max cache layers const Setting sMaxCacheLayers = { QgsServerSettingsEnv::MAX_CACHE_LAYERS, QgsServerSettingsEnv::DEFAULT_VALUE, - "Specify the maximum number of cached layers", - "", + QStringLiteral( "Specify the maximum number of cached layers" ), + QString(), QVariant::Int, QVariant( 100 ), QVariant() @@ -121,8 +121,8 @@ void QgsServerSettings::initSettings() // cache directory const Setting sCacheDir = { QgsServerSettingsEnv::QGIS_SERVER_CACHE_DIRECTORY, QgsServerSettingsEnv::DEFAULT_VALUE, - "Specify the cache directory", - "/cache/directory", + QStringLiteral( "Specify the cache directory" ), + QStringLiteral( "/cache/directory" ), QVariant::String, QVariant( QgsApplication::qgisSettingsDirPath() + "cache" ), QVariant() @@ -132,8 +132,8 @@ void QgsServerSettings::initSettings() // cache size const Setting sCacheSize = { QgsServerSettingsEnv::QGIS_SERVER_CACHE_SIZE, QgsServerSettingsEnv::DEFAULT_VALUE, - "Specify the cache size", - "/cache/size", + QStringLiteral( "Specify the cache size" ), + QStringLiteral( "/cache/size" ), QVariant::LongLong, QVariant( 50 * 1024 * 1024 ), QVariant() @@ -162,6 +162,27 @@ void QgsServerSettings::initSettings() }; mSettings[ sShowGroupSeparator.envVar ] = sShowGroupSeparator; + // max height + const Setting sMaxHeight = { QgsServerSettingsEnv::QGIS_SERVER_WMS_MAX_HEIGHT, + QgsServerSettingsEnv::DEFAULT_VALUE, + QStringLiteral( "Maximum height for a WMS request. The lower one of this and the project configuration is used." ), + QStringLiteral( "/qgis/max_wms_height" ), + QVariant::LongLong, + QVariant( -1 ), + QVariant() + }; + mSettings[ sMaxHeight.envVar ] = sMaxHeight; + + // max width + const Setting sMaxWidth = { QgsServerSettingsEnv::QGIS_SERVER_WMS_MAX_WIDTH, + QgsServerSettingsEnv::DEFAULT_VALUE, + QStringLiteral( "Maximum width for a WMS request. The most conservative between this and the project one is used" ), + QStringLiteral( "/qgis/max_wms_width" ), + QVariant::LongLong, + QVariant( -1 ), + QVariant() + }; + mSettings[ sMaxWidth.envVar ] = sMaxWidth; } void QgsServerSettings::load() @@ -353,3 +374,12 @@ bool QgsServerSettings::showGroupSeparator() const return value( QgsServerSettingsEnv::QGIS_SERVER_SHOW_GROUP_SEPARATOR ).toBool(); } +int QgsServerSettings::wmsMaxHeight() const +{ + return value( QgsServerSettingsEnv::QGIS_SERVER_WMS_MAX_HEIGHT ).toInt(); +} + +int QgsServerSettings::wmsMaxWidth() const +{ + return value( QgsServerSettingsEnv::QGIS_SERVER_WMS_MAX_WIDTH ).toInt(); +} diff --git a/src/server/qgsserversettings.h b/src/server/qgsserversettings.h index fd9cfd9e2f77..fc8e67d53c0f 100644 --- a/src/server/qgsserversettings.h +++ b/src/server/qgsserversettings.h @@ -63,6 +63,8 @@ class SERVER_EXPORT QgsServerSettingsEnv : public QObject QGIS_SERVER_CACHE_SIZE, QGIS_SERVER_SHOW_GROUP_SEPARATOR, //! Show group (thousands) separator when formatting numeric values, defaults to FALSE (since QGIS 3.8) QGIS_SERVER_OVERRIDE_SYSTEM_LOCALE, //! Override system locale (since QGIS 3.8) + QGIS_SERVER_WMS_MAX_HEIGHT, //! Maximum height for a WMS request. The most conservative between this and the project one is used (since QGIS 3.8) + QGIS_SERVER_WMS_MAX_WIDTH //! Maximum width for a WMS request. The most conservative between this and the project one is used (since QGIS 3.8) }; Q_ENUM( EnvVar ) }; @@ -184,6 +186,20 @@ class SERVER_EXPORT QgsServerSettings */ bool showGroupSeparator() const; + /** + * Returns the server-wide max height of a WMS GetMap request. The lower one of this and the project configuration is used. + * \returns the max height of a WMS GetMap request. + * \since QGIS 3.8 + */ + int wmsMaxHeight() const; + + /** + * Returns the server-wide max width of a WMS GetMap request. The lower one of this and the project configuration is used. + * \returns the max width of a WMS GetMap request. + * \since QGIS 3.8 + */ + int wmsMaxWidth() const; + private: void initSettings(); QVariant value( QgsServerSettingsEnv::EnvVar envVar ) const; diff --git a/src/server/services/wms/qgswmsrenderer.cpp b/src/server/services/wms/qgswmsrenderer.cpp index d3e1b94b0e69..a92b00512361 100644 --- a/src/server/services/wms/qgswmsrenderer.cpp +++ b/src/server/services/wms/qgswmsrenderer.cpp @@ -2053,21 +2053,51 @@ namespace QgsWms bool QgsRenderer::checkMaximumWidthHeight() const { - //test if maxWidth / maxHeight set and WIDTH / HEIGHT parameter is in the range - int wmsMaxWidth = QgsServerProjectUtils::wmsMaxWidth( *mProject ); - int width = mWmsParameters.widthAsInt(); + //test if maxWidth / maxHeight are set in the project or as an env variable + //and WIDTH / HEIGHT parameter is in the range allowed range + //WIDTH + int wmsMaxWidthProj = QgsServerProjectUtils::wmsMaxWidth( *mProject ); + int wmsMaxWidthEnv = mContext.settings().wmsMaxWidth(); + int wmsMaxWidth; + if ( wmsMaxWidthEnv != -1 && wmsMaxWidthProj != -1 ) + { + // both are set, so we take the more conservative one + wmsMaxWidth = std::min( wmsMaxWidthProj, wmsMaxWidthEnv ); + } + else + { + // none or one are set, so we take the bigger one which is the one set or -1 + wmsMaxWidth = std::max( wmsMaxWidthProj, wmsMaxWidthEnv ); + } + + int width = this->width(); if ( wmsMaxWidth != -1 && width > wmsMaxWidth ) { return false; } - int wmsMaxHeight = QgsServerProjectUtils::wmsMaxHeight( *mProject ); - int height = mWmsParameters.heightAsInt(); + //HEIGHT + int wmsMaxHeightProj = QgsServerProjectUtils::wmsMaxHeight( *mProject ); + int wmsMaxHeightEnv = mContext.settings().wmsMaxHeight(); + int wmsMaxHeight; + if ( wmsMaxWidthEnv != -1 && wmsMaxWidthProj != -1 ) + { + // both are set, so we take the more conservative one + wmsMaxHeight = std::min( wmsMaxHeightProj, wmsMaxHeightEnv ); + } + else + { + // none or one are set, so we take the bigger one which is the one set or -1 + wmsMaxHeight = std::max( wmsMaxHeightProj, wmsMaxHeightEnv ); + } + + int height = this->height(); if ( wmsMaxHeight != -1 && height > wmsMaxHeight ) { return false; } + // Sanity check from internal QImage checks (see qimage.cpp) // this is to report a meaningful error message in case of // image creation failure and to differentiate it from out diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index f00bb8a10863..c10a7942e72e 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -280,6 +280,8 @@ IF (WITH_SERVER) ADD_PYTHON_TEST(PyQgsServerPlugins test_qgsserver_plugins.py) ADD_PYTHON_TEST(PyQgsServerWMS test_qgsserver_wms.py) ADD_PYTHON_TEST(PyQgsServerWMSGetMap test_qgsserver_wms_getmap.py) + ADD_PYTHON_TEST(PyQgsServerWMSGetMapSizeProject test_qgsserver_wms_getmap_size_project.py) + ADD_PYTHON_TEST(PyQgsServerWMSGetMapSizeServer test_qgsserver_wms_getmap_size_server.py) ADD_PYTHON_TEST(PyQgsServerWMSGetFeatureInfo test_qgsserver_wms_getfeatureinfo.py) ADD_PYTHON_TEST(PyQgsServerWMSGetLegendGraphic test_qgsserver_wms_getlegendgraphic.py) ADD_PYTHON_TEST(PyQgsServerWMSGetPrint test_qgsserver_wms_getprint.py) diff --git a/tests/src/python/test_qgsserver_wms_getmap_size_project.py b/tests/src/python/test_qgsserver_wms_getmap_size_project.py new file mode 100644 index 000000000000..d9acd7398c53 --- /dev/null +++ b/tests/src/python/test_qgsserver_wms_getmap_size_project.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsServer MaxHeight and MaxWidth Override Options. + +From build dir, run: ctest -R PyQgsServerGetMapSize -V + +.. note:: This test needs env vars to be set before the server is + configured for the first time, for this + reason it cannot run as a test case of another server + test. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +""" +__author__ = 'Marco Bernasocchi' +__date__ = '01/04/2019' +__copyright__ = 'Copyright 2019, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import os + +# Needed on Qt 5 so that the serialization of XML is consistent among all +# executions +os.environ['QT_HASH_SEED'] = '1' + + +import urllib.parse + +from qgis.testing import unittest + +from test_qgsserver import QgsServerTestBase + + +class TestQgsServerWMSGetMapSizeProject(QgsServerTestBase): + """QGIS Server WMS Tests for GetFeatureInfo request""" + + # Set to True to re-generate reference files for this class + regenerate_reference = False + + def setUp(self): + os.environ['QGIS_SERVER_WMS_MAX_WIDTH'] = '6000' + os.environ['QGIS_SERVER_WMS_MAX_HEIGHT'] = '6000' + super(TestQgsServerWMSGetMapSizeProject, self).setUp() + self.project = os.path.join(self.testdata_path, "test_project_with_size.qgs") + self.expected_too_big = self.strip_version_xmlns(b'\n The requested map size is too large\n\n') + + def test_wms_getmap_invalid_size_project(self): + # test the 6000 limit from server is overridden by the more conservative 5000 in the project + r = make_request(self, 5001, 5000) + self.assertEqual(self.strip_version_xmlns(r), self.expected_too_big) + + +def make_request(instance, height, width): + qs = "?" + "&".join(["%s=%s" % i for i in list({ + "MAP": urllib.parse.quote(instance.project), + "SERVICE": "WMS", + "VERSION": "1.3.0", + "REQUEST": "GetMap", + "LAYERS": "", + "STYLES": "", + "FORMAT": "image/png", + "HEIGHT": str(height), + "WIDTH": str(width) + }.items())]) + r, h = instance._result(instance._execute_request(qs)) + return instance.strip_version_xmlns(r) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgsserver_wms_getmap_size_server.py b/tests/src/python/test_qgsserver_wms_getmap_size_server.py new file mode 100644 index 000000000000..67285d9780c7 --- /dev/null +++ b/tests/src/python/test_qgsserver_wms_getmap_size_server.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsServer MaxHeight and MaxWidth Override Options. + +From build dir, run: ctest -R PyQgsServerGetMapSize -V + +.. note:: This test needs env vars to be set before the server is + configured for the first time, for this + reason it cannot run as a test case of another server + test. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +""" +__author__ = 'Marco Bernasocchi' +__date__ = '01/04/2019' +__copyright__ = 'Copyright 2019, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import os + +# Needed on Qt 5 so that the serialization of XML is consistent among all +# executions +os.environ['QT_HASH_SEED'] = '1' + +from qgis.testing import unittest + +from test_qgsserver import QgsServerTestBase +from test_qgsserver_wms_getmap_size_project import make_request + + +class TestQgsServerWMSGetMapSizeServer(QgsServerTestBase): + """QGIS Server WMS Tests for GetFeatureInfo request""" + + # Set to True to re-generate reference files for this class + regenerate_reference = False + + def setUp(self): + os.environ['QGIS_SERVER_WMS_MAX_WIDTH'] = '3000' + os.environ['QGIS_SERVER_WMS_MAX_HEIGHT'] = '3000' + super(TestQgsServerWMSGetMapSizeServer, self).setUp() + self.project = os.path.join(self.testdata_path, "test_project_with_size.qgs") + self.expected_too_big = self.strip_version_xmlns(b'\n The requested map size is too large\n\n') + + def test_wms_getmap_invalid_size_server(self): + # test the 3000 limit from server is overriding the less conservative 5000 in the project + r = make_request(self, 3001, 3000) + self.assertEqual(self.strip_version_xmlns(r), self.expected_too_big) + + +if __name__ == '__main__': + unittest.main()