Skip to content

Commit

Permalink
Merge pull request #9691 from mbernasocchi/check_width_height
Browse files Browse the repository at this point in the history
Allow setting wmsMaxWidth and wmsMaxHeight in the server env
  • Loading branch information
mbernasocchi committed Apr 5, 2019
2 parents 23b6c67 + fad4732 commit 8b3f97f
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 42 deletions.
18 changes: 18 additions & 0 deletions python/server/auto_generated/qgsserversettings.sip.in
Expand Up @@ -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

Expand Down
70 changes: 50 additions & 20 deletions src/server/qgsserversettings.cpp
Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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();
}
16 changes: 16 additions & 0 deletions src/server/qgsserversettings.h
Expand Up @@ -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 )
};
Expand Down Expand Up @@ -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;
Expand Down
36 changes: 33 additions & 3 deletions src/server/services/wms/qgswmsrenderer.cpp
Expand Up @@ -1870,21 +1870,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 );
//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 );
//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
Expand Down
2 changes: 2 additions & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -286,6 +286,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)
Expand Down
19 changes: 0 additions & 19 deletions tests/src/python/test_qgsserver_wms_getmap.py
Expand Up @@ -615,25 +615,6 @@ def test_wms_getmap_background(self):
r, h = self._result(self._execute_request(qs))
self._img_diff_error(r, h, "WMS_GetMap_Background_Hex")

def test_wms_getmap_invalid_size(self):
project = os.path.join(self.testdata_path, "test_project_with_size.qgs")
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(project),
"SERVICE": "WMS",
"VERSION": "1.3.0",
"REQUEST": "GetMap",
"LAYERS": "",
"STYLES": "",
"FORMAT": "image/png",
"HEIGHT": "5001",
"WIDTH": "5000"
}.items())])

expected = self.strip_version_xmlns(b'<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="InvalidParameterValue">The requested map size is too large</ServiceException>\n</ServiceExceptionReport>\n')
r, h = self._result(self._execute_request(qs))

self.assertEqual(self.strip_version_xmlns(r), expected)

def test_wms_getmap_order(self):
qs = "?" + "&".join(["%s=%s" % i for i in list({
"MAP": urllib.parse.quote(self.projectPath),
Expand Down
73 changes: 73 additions & 0 deletions 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'<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="InvalidParameterValue">The requested map size is too large</ServiceException>\n</ServiceExceptionReport>\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()

0 comments on commit 8b3f97f

Please sign in to comment.