diff --git a/docs/samples/config/mswms/mswms_settings.py.sample b/docs/samples/config/mswms/mswms_settings.py.sample
index 94a5eec6e..f87a0f00d 100644
--- a/docs/samples/config/mswms/mswms_settings.py.sample
+++ b/docs/samples/config/mswms/mswms_settings.py.sample
@@ -108,6 +108,23 @@ basemap_use_cache = False
basemap_request_size = 200
basemap_cache_size = 20
+#
+# Request limits ###
+#
+
+# A GetMap request beyond these limits gets a ServiceException instead of a plot,
+# so that a single request cannot use up the memory of the server.
+# 'max_image_width' and 'max_image_height' limit the image size in pixels (msui
+# requests the size of the plot on screen), 'max_layers' the number of LAYERS in
+# one request, each layer can be requested once per style. 'max_section_points' limits the number of interpolation points and
+# labels of vertical and linear sections (msui option 'num_interpolation_points'),
+# 'max_path_waypoints' the number of waypoints in PATH.
+max_image_width = 4096
+max_image_height = 4096
+max_layers = 10
+max_section_points = 2000
+max_path_waypoints = 1000
+
#
# Registration of horizontal layers. ###
#
diff --git a/mslib/mswms/app/__init__.py b/mslib/mswms/app/__init__.py
index f1f056b08..dfa97ed53 100644
--- a/mslib/mswms/app/__init__.py
+++ b/mslib/mswms/app/__init__.py
@@ -74,6 +74,12 @@ class default_mswms_settings:
GDPR = ""
data = {}
ENABLE_BASIC_HTTP_AUTHENTICATION = False
+ # Limits of a GetMap request, see the sample mswms_settings.py
+ max_image_width = 4096
+ max_image_height = 4096
+ max_layers = 10
+ max_section_points = 2000
+ max_path_waypoints = 1000
__file__ = None
diff --git a/mslib/mswms/mpl_hsec.py b/mslib/mswms/mpl_hsec.py
index 0b0a5eb50..b8ef3be23 100644
--- a/mslib/mswms/mpl_hsec.py
+++ b/mslib/mswms/mpl_hsec.py
@@ -200,8 +200,8 @@ def _draw_auto_graticule(self, bm):
spacingValues = [0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 20, 30, 40]
deltaLon = mapLonStop - mapLonStart
deltaLat = mapLatStop - mapLatStart
- spacingLon = [i for i in spacingValues if i > (deltaLon / 10.)][0]
- spacingLat = [i for i in spacingValues if i > (deltaLat / 10.)][0]
+ spacingLon = next((i for i in spacingValues if i > (deltaLon / 10.)), spacingValues[-1])
+ spacingLat = next((i for i in spacingValues if i > (deltaLat / 10.)), spacingValues[-1])
# c) parallels and meridians start at the first value in the
# spacingLon/Lat grid that's smaller than the lon/lat of the
@@ -302,7 +302,7 @@ def plot_hsection(self, data, lats, lons, bbox=(-180, -90, 180, 90),
# access.
key = repr((proj_params, bbox, bbox_units))
basemap_use_cache = getattr(mswms_settings, "basemap_use_cache", False)
- basemap_request_size = getattr(mswms_settings, "basemap_request_size ", 200)
+ basemap_request_size = getattr(mswms_settings, "basemap_request_size", 200)
basemap_cache_size = getattr(mswms_settings, "basemap_cache_size", 20)
bm_params = {"area_thresh": 1000., "ax": ax, "fix_aspect": (not noframe)}
bm_params.update(proj_params)
diff --git a/mslib/mswms/wms.py b/mslib/mswms/wms.py
index c6cea1f8e..15c48fd36 100644
--- a/mslib/mswms/wms.py
+++ b/mslib/mswms/wms.py
@@ -46,6 +46,7 @@
import io
import inspect
import logging
+import math
import shutil
import tempfile
import traceback
@@ -143,6 +144,78 @@ def squash_multiple_xml(xml_strings):
return ElementTree.tostring(base)
+class InvalidRequestValue(ValueError):
+ """
+ A GetMap parameter is invalid, e.g. not a finite number or outside the limits set in mswms_settings
+
+ produce_plot answers it with a ServiceException. code is the WMS exception code, None for none.
+ """
+ def __init__(self, text, code=None):
+ super().__init__(text)
+ self.code = code
+
+
+def check_range(number, name, lowest=-math.inf, highest=math.inf, integer=False):
+ """
+ Returns number, an int if integer is True, if it is in the range lowest..highest.
+ Raises InvalidRequestValue otherwise.
+ """
+ if integer and not float(number).is_integer():
+ raise InvalidRequestValue(f"{name} must be an integer, got '{number}'")
+ if not lowest <= number <= highest:
+ raise InvalidRequestValue(f"{name} must be between {lowest} and {highest}, got '{number}'")
+ return int(number) if integer else number
+
+
+def parse_number(value, name, lowest=-math.inf, highest=math.inf, integer=False):
+ """
+ Converts a query value to a finite float (to an int if integer is True)
+ in the range lowest..highest. Raises InvalidRequestValue otherwise.
+ """
+ try:
+ number = float(value)
+ except (TypeError, ValueError):
+ raise InvalidRequestValue(f"Invalid {name}: '{value}'")
+ if not math.isfinite(number):
+ raise InvalidRequestValue(f"{name} must be a finite number, got '{value}'")
+ return check_range(number, name, lowest, highest, integer)
+
+
+def parse_numbers(values, name, count=None, min_count=None, max_count=None, multiple_of=1):
+ """
+ Converts a comma separated query value to a list of finite floats. count
+ is the required number of values, min_count and max_count the minimum and
+ maximum number, multiple_of what the number has to be a multiple of, e.g.
+ 2 for lat/lon pairs. The number is checked before the string is split.
+ """
+ number = values.count(",") + 1
+ if count is not None and number != count:
+ raise InvalidRequestValue(f"{name} needs {count} values, got {number}")
+ if max_count is not None and number > max_count:
+ raise InvalidRequestValue(f"{name} has more than {max_count} values")
+ if min_count is not None and number < min_count:
+ raise InvalidRequestValue(f"{name} needs at least {min_count} values, got {number}")
+ if number % multiple_of != 0:
+ raise InvalidRequestValue(f"{name} needs a multiple of {multiple_of} values, got {number}")
+ return [parse_number(value, name) for value in values.split(",")]
+
+
+def check_bbox(bbox, bbox_units):
+ """
+ Checks the BBOX (x_min, y_min, x_max, y_max) of a horizontal section, already in x/y order
+
+ The box must not be empty. In degrees the latitudes are in -90..90 and the box spans at most 360 degrees of
+ longitude, beyond these the map and its grid lines can't be drawn.
+ """
+ if not (bbox[0] < bbox[2] and bbox[1] < bbox[3]):
+ raise InvalidRequestValue(f"BBOX must have minimum values smaller than its maximum values, got {bbox}")
+ if bbox_units == "degree":
+ if not (-90 <= bbox[1] and bbox[3] <= 90):
+ raise InvalidRequestValue(f"BBOX latitudes must be between -90 and 90, got {bbox}")
+ if bbox[2] - bbox[0] > 360:
+ raise InvalidRequestValue(f"BBOX must span at most 360 degrees of longitude, got {bbox}")
+
+
class WMSServer:
def __init__(self):
@@ -618,7 +691,10 @@ def get_capabilities(self, query, server_url=None):
service_post_code=mswms_settings.service_post_code,
service_country=mswms_settings.service_country,
service_fees=mswms_settings.service_fees,
- service_access_constraints=mswms_settings.service_access_constraints)
+ service_access_constraints=mswms_settings.service_access_constraints,
+ max_layers=mswms_settings.max_layers,
+ max_image_width=mswms_settings.max_image_width,
+ max_image_height=mswms_settings.max_image_height)
return return_data.encode("utf-8"), "text/xml"
def produce_plot(self, query, mode):
@@ -627,6 +703,12 @@ def produce_plot(self, query, mode):
the parameters specified in the URL.
"""
+ try:
+ return self._produce_plot(query, mode)
+ except InvalidRequestValue as ex:
+ return self.create_service_exception(code=ex.code, text=str(ex), version=query.get("VERSION", "1.1.1"))
+
+ def _produce_plot(self, query, mode):
logging.debug("GetMap/GetVSec request. Interpreting parameters..")
# Evaluate query parameters:
@@ -634,14 +716,28 @@ def produce_plot(self, query, mode):
version = query.get("VERSION", "1.1.1")
- # Image size.
- width = query.get('WIDTH', 900)
- height = query.get('HEIGHT', 600)
- figsize = float(width if width != "" else 900), float(height if height != "" else 600)
- logging.debug(" requested image size = %sx%s", figsize[0], figsize[1])
+ def requested_figsize():
+ # Image size, only of the modes that draw an image
+ width = query.get('WIDTH') or 900
+ height = query.get('HEIGHT') or 600
+ figsize = (parse_number(width, "WIDTH", 1, mswms_settings.max_image_width),
+ parse_number(height, "HEIGHT", 1, mswms_settings.max_image_height))
+ logging.debug(" requested image size = %sx%s", figsize[0], figsize[1])
+ return figsize
# Requested layers.
layers = [layer for layer in query.get('LAYERS', '').strip().split(',') if layer]
+ if not layers:
+ return self.create_service_exception(code="LayerNotDefined", text="LAYERS not specified",
+ version=version)
+ if len(layers) > mswms_settings.max_layers:
+ raise InvalidRequestValue(f"At most {mswms_settings.max_layers} LAYERS can be requested at once")
+ # Requested style(s).
+ styles = [style for style in query.get('STYLES', 'default').strip().split(',') if style]
+ # each layer is drawn once per style, repeating it would multiply the cost of the request
+ layer_styles = [(layer, styles[index] if len(styles) > index else None) for index, layer in enumerate(layers)]
+ if len(set(layer_styles)) != len(layer_styles):
+ raise InvalidRequestValue("LAYERS must not contain the same layer with the same style more than once")
images = []
for index, layer in enumerate(layers):
if layer.find(".") > 0:
@@ -650,9 +746,7 @@ def produce_plot(self, query, mode):
dataset = None
logging.debug(" requested dataset = '%s', layer = '%s'", dataset, layer)
- # Requested style(s).
- styles = [style for style in query.get('STYLES', 'default').strip().split(',') if style]
- style = styles[index] if len(styles) > index else None
+ style = layer_styles[index][1]
logging.debug(" requested style = '%s'", style)
# Forecast initialisation time.
@@ -691,7 +785,7 @@ def produce_plot(self, query, mode):
mode = "getlsec"
else:
try:
- get_projection_params(crs)
+ bbox_units = get_projection_params(crs)["bbox"]
except ValueError:
return self.create_service_exception(
code="InvalidSRS", text=f"The requested CRS '{crs}' is not supported.", version=version)
@@ -748,19 +842,20 @@ def produce_plot(self, query, mode):
version=version)
# Bounding box.
- try:
- if is_yx:
- bbox = [float(v) for v in query.get('BBOX', '-90,-180,90,180').split(',')]
- bbox = (bbox[1], bbox[0], bbox[3], bbox[2])
- else:
- bbox = [float(v) for v in query.get('BBOX', '-180,-90,180,90').split(',')]
-
- except ValueError:
- return self.create_service_exception(text=f"Invalid BBOX: {query.get('BBOX')}", version=version)
+ if is_yx:
+ bbox = parse_numbers(query.get('BBOX', '-90,-180,90,180'), "BBOX", count=4)
+ bbox = (bbox[1], bbox[0], bbox[3], bbox[2])
+ else:
+ bbox = parse_numbers(query.get('BBOX', '-180,-90,180,90'), "BBOX", count=4)
+ check_bbox(bbox, bbox_units)
+ figsize = requested_figsize()
# Vertical level, if applicable.
level = query.get('ELEVATION')
- level = float(level) if level is not None else None
+ try:
+ level = parse_number(level, "ELEVATION") if level is not None else None
+ except InvalidRequestValue as ex:
+ raise InvalidRequestValue(str(ex), code="InvalidDimensionValue")
layer_datatypes = self.hsec_layer_registry[dataset][layer].required_datatypes()
from mslib.utils.netCDF4tools import VERTICAL_AXIS
if level is None and any(_x in layer_datatypes for _x in VERTICAL_AXIS):
@@ -793,11 +888,10 @@ def produce_plot(self, query, mode):
path = query.get("PATH")
if path is None:
return self.create_service_exception(text="PATH not specified", version=version)
- try:
- path = [float(v) for v in path.split(',')]
- path = [[lat, lon] for lat, lon in zip(path[0::2], path[1::2])]
- except ValueError:
- return self.create_service_exception(text=f"Invalid PATH: {path}", version=version)
+ # at least two waypoints of lat, lon
+ path = parse_numbers(path, "PATH", min_count=4, max_count=2 * mswms_settings.max_path_waypoints,
+ multiple_of=2)
+ path = [[lat, lon] for lat, lon in zip(path[0::2], path[1::2])]
logging.debug("VSEC PATH: %s", path)
# Check requested layers.
@@ -820,10 +914,12 @@ def produce_plot(self, query, mode):
version=version)
# Bounding box (num interp. points, p_bot, num labels, p_top).
- try:
- bbox = [float(v) for v in query.get("BBOX", "101,1050,10,180").split(",")]
- except ValueError:
- return self.create_service_exception(text=f"Invalid BBOX: {query.get('BBOX')}", version=version)
+ bbox = parse_numbers(query.get("BBOX", "101,1050,10,180"), "BBOX", count=4)
+ numpoints = check_range(bbox[0], "BBOX number of points",
+ 2, mswms_settings.max_section_points, integer=True)
+ numlabels = check_range(bbox[2], "BBOX number of labels",
+ 1, mswms_settings.max_section_points, integer=True)
+ figsize = requested_figsize()
draw_verticals = query.get("DRAWVERTICALS", "false").lower() == "true"
@@ -831,9 +927,9 @@ def produce_plot(self, query, mode):
try:
plot_driver.set_plot_parameters(plot_object=self.vsec_layer_registry[dataset][layer],
vsec_path=path,
- vsec_numpoints=bbox[0],
+ vsec_numpoints=numpoints,
vsec_path_connection="greatcircle",
- vsec_numlabels=bbox[2],
+ vsec_numlabels=numlabels,
init_time=init_time,
valid_time=valid_time,
style=style,
@@ -863,11 +959,10 @@ def produce_plot(self, query, mode):
path = query.get("PATH")
if path is None:
return self.create_service_exception(text="PATH not specified", version=version)
- try:
- path = [float(v) for v in path.split(',')]
- path = [[lat, lon, alt] for lat, lon, alt in zip(path[0::3], path[1::3], path[2::3])]
- except ValueError:
- return self.create_service_exception(text=f"Invalid PATH: {path}", version=version)
+ # at least two waypoints of lat, lon, alt
+ path = parse_numbers(path, "PATH", min_count=6, max_count=3 * mswms_settings.max_path_waypoints,
+ multiple_of=3)
+ path = [[lat, lon, alt] for lat, lon, alt in zip(path[0::3], path[1::3], path[2::3])]
logging.debug("LSEC PATH: %s", path)
# Check requested layers.
@@ -890,10 +985,8 @@ def produce_plot(self, query, mode):
version=version)
# Bounding box (num interp. points).
- try:
- bbox = float(query.get("BBOX", "101"))
- except ValueError:
- return self.create_service_exception(text=f"Invalid BBOX: {query.get('BBOX')}", version=version)
+ bbox = parse_number(query.get("BBOX", "101"), "BBOX number of points",
+ 2, mswms_settings.max_section_points, integer=True)
plot_driver = self.lsec_drivers[dataset]
try:
diff --git a/mslib/mswms/xml_templates/get_capabilities130.pt b/mslib/mswms/xml_templates/get_capabilities130.pt
index 2e5f36ede..23170b063 100644
--- a/mslib/mswms/xml_templates/get_capabilities130.pt
+++ b/mslib/mswms/xml_templates/get_capabilities130.pt
@@ -28,6 +28,9 @@
${ service_fees }
${ service_access_constraints }
+ ${ max_layers }
+ ${ max_image_width }
+ ${ max_image_height }
diff --git a/tests/_test_mswms/test_wms.py b/tests/_test_mswms/test_wms.py
index db5e7af62..71af6ba08 100644
--- a/tests/_test_mswms/test_wms.py
+++ b/tests/_test_mswms/test_wms.py
@@ -39,6 +39,22 @@
from tests.constants import MSWMS_DATA_DIR
+HSEC_QUERY = (
+ 'layers=ecmwf_EUR_LL015.PLDiv01&styles=&elevation=200&srs=EPSG%3A4326&format=image%2Fpng&'
+ 'request=GetMap&height=376&dim_init_time=2012-10-17T12%3A00%3A00Z&width=479&'
+ 'version=1.1.1&bbox=-50.0%2C20.0%2C20.0%2C75.0&time=2012-10-17T12%3A00%3A00Z&transparent=FALSE')
+VSEC_QUERY = (
+ 'layers=ecmwf_EUR_LL015.VS_HV01&styles=&srs=VERT%3ALOGP&format=image%2Fpng&'
+ 'request=GetMap&height=245&dim_init_time=2012-10-17T12%3A00%3A00Z&width=842&'
+ 'version=1.1.1&bbox=201%2C500.0%2C10%2C100.0&time=2012-10-17T12%3A00%3A00Z&'
+ 'path=52.78%2C-8.93%2C48.08%2C11.28&transparent=FALSE')
+LSEC_QUERY = (
+ 'layers=ecmwf_EUR_LL015.LS_HV01&styles=&srs=LINE%3A1&format=text%2Fxml&'
+ 'request=GetMap&dim_init_time=2012-10-17T12%3A00%3A00Z&'
+ 'version=1.1.1&bbox=201&time=2012-10-17T12%3A00%3A00Z&'
+ 'path=52.78%2C-8.93%2C25000%2C48.08%2C11.28%2C25000')
+
+
class Test_WMS:
@pytest.fixture(autouse=True)
def setup(self, mswms_app):
@@ -377,6 +393,111 @@ def test_multiple_xml(self):
result = self.client.get('/?{}'.format(environ["QUERY_STRING"]))
callback_ok_xml(result.status, result.headers)
+ @pytest.mark.parametrize("query, orig, fake, message", [
+ (HSEC_QUERY, "width=479", "width=65000", b"WIDTH must be between 1 and 4096"),
+ (HSEC_QUERY, "height=376", "height=65000", b"HEIGHT must be between 1 and 4096"),
+ (VSEC_QUERY, "width=842", "width=4097", b"WIDTH must be between 1 and 4096"),
+ (HSEC_QUERY, "width=479", "width=0", b"WIDTH must be between"),
+ (HSEC_QUERY, "width=479", "width=-479", b"WIDTH must be between"),
+ (HSEC_QUERY, "width=479", "width=nan", b"WIDTH must be a finite number"),
+ (HSEC_QUERY, "height=376", "height=inf", b"HEIGHT must be a finite number"),
+ (HSEC_QUERY, "width=479", "width=abc", b"Invalid WIDTH"),
+ (HSEC_QUERY, "layers=ecmwf_EUR_LL015.PLDiv01", "layers=" + ",".join(["ecmwf_EUR_LL015.PLDiv01"] * 11),
+ b"At most 10 LAYERS"),
+ (HSEC_QUERY, "layers=ecmwf_EUR_LL015.PLDiv01", "layers=ecmwf_EUR_LL015.PLDiv01,ecmwf_EUR_LL015.PLDiv01",
+ b"LAYERS must not contain the same layer with the same style more than once"),
+ (HSEC_QUERY, "bbox=-50.0%2C20.0%2C20.0%2C75.0", "bbox=-1000%2C-90%2C1000%2C90",
+ b"BBOX must span at most 360 degrees of longitude"),
+ (HSEC_QUERY, "bbox=-50.0%2C20.0%2C20.0%2C75.0", "bbox=0%2C0%2C0%2C0",
+ b"BBOX must have minimum values smaller than its maximum values"),
+ (HSEC_QUERY, "bbox=-50.0%2C20.0%2C20.0%2C75.0", "bbox=20.0%2C20.0%2C-50.0%2C75.0",
+ b"BBOX must have minimum values smaller than its maximum values"),
+ (HSEC_QUERY, "bbox=-50.0%2C20.0%2C20.0%2C75.0", "bbox=-50.0%2C20.0%2C20.0%2C95.0",
+ b"BBOX latitudes must be between -90 and 90"),
+ (HSEC_QUERY, "layers=ecmwf_EUR_LL015.PLDiv01", "layers=", b"LAYERS not specified"),
+ (HSEC_QUERY, "bbox=-50.0", "bbox=nan", b"BBOX must be a finite number"),
+ (HSEC_QUERY, "bbox=-50.0%2C", "bbox=", b"BBOX needs 4 values, got 3"),
+ (HSEC_QUERY, "elevation=200", "elevation=abc", b"Invalid ELEVATION"),
+ (HSEC_QUERY, "elevation=200", "elevation=-inf", b"ELEVATION must be a finite number"),
+ (VSEC_QUERY, "bbox=201", "bbox=1e8", b"BBOX number of points must be between 2 and 2000"),
+ (VSEC_QUERY, "bbox=201", "bbox=1", b"BBOX number of points must be between 2 and 2000"),
+ (VSEC_QUERY, "bbox=201", "bbox=201.5", b"BBOX number of points must be an integer"),
+ (VSEC_QUERY, "bbox=201", "bbox=nan", b"BBOX must be a finite number"),
+ (VSEC_QUERY, "%2C10%2C", "%2C0%2C", b"BBOX number of labels must be between 1 and 2000"),
+ (VSEC_QUERY, "%2C10%2C", "%2C1e9%2C", b"BBOX number of labels must be between 1 and 2000"),
+ (VSEC_QUERY, "500.0", "inf", b"BBOX must be a finite number"),
+ (VSEC_QUERY, "%2C100.0", "", b"BBOX needs 4 values, got 3"),
+ (VSEC_QUERY, "path=52.78%2C-8.93%2C48.08%2C11.28", "path=" + "%2C".join(["52.78%2C-8.93"] * 1001),
+ b"PATH has more than 2000 values"),
+ (VSEC_QUERY, "path=52.78", "path=inf", b"PATH must be a finite number"),
+ (VSEC_QUERY, "path=52.78%2C-8.93%2C48.08%2C11.28", "path=5", b"PATH needs at least 4 values, got 1"),
+ (VSEC_QUERY, "path=52.78%2C-8.93%2C48.08%2C11.28", "path=52.78%2C-8.93%2C48.08",
+ b"PATH needs at least 4 values, got 3"),
+ (VSEC_QUERY, "path=52.78%2C-8.93%2C48.08%2C11.28", "path=52.78%2C-8.93%2C48.08%2C11.28%2C50",
+ b"PATH needs a multiple of 2 values, got 5"),
+ (LSEC_QUERY, "bbox=201", "bbox=5e6", b"BBOX number of points must be between 2 and 2000"),
+ (LSEC_QUERY, "bbox=201", "bbox=nan", b"BBOX number of points must be a finite number"),
+ (LSEC_QUERY, "path=52.78%2C-8.93%2C25000%2C48.08%2C11.28%2C25000",
+ "path=" + "%2C".join(["52.78%2C-8.93%2C25000"] * 1001), b"PATH has more than 3000 values"),
+ (LSEC_QUERY, "path=52.78", "path=nan", b"PATH must be a finite number"),
+ (LSEC_QUERY, "path=52.78%2C-8.93%2C25000%2C48.08%2C11.28%2C25000", "path=52.78%2C-8.93%2C25000",
+ b"PATH needs at least 6 values, got 3"),
+ (LSEC_QUERY, "path=52.78%2C-8.93%2C25000%2C48.08%2C11.28%2C25000",
+ "path=52.78%2C-8.93%2C25000%2C48.08%2C11.28%2C25000%2C1", b"PATH needs a multiple of 3 values, got 7"),
+ ])
+ def test_produce_plot_rejects_values_beyond_limits(self, query, orig, fake, message):
+ assert orig in query
+ result = self.app.test_client().get('/?{}'.format(query.replace(orig, fake, 1)))
+ callback_ok_xml(result.status, result.headers)
+ assert b"ServiceExceptionReport" in result.data, result.data
+ assert message in result.data, result.data
+ # InvalidParameterValue is no exception code of WMS 1.1.1 or 1.3.0
+ assert b"InvalidParameterValue" not in result.data, result.data
+
+ @pytest.mark.parametrize("query, setting, value", [
+ (HSEC_QUERY, "max_image_width", 479),
+ (HSEC_QUERY, "max_image_height", 376),
+ (HSEC_QUERY, "max_layers", 1),
+ (VSEC_QUERY, "max_section_points", 201),
+ (VSEC_QUERY, "max_path_waypoints", 2),
+ (LSEC_QUERY, "max_section_points", 201),
+ (LSEC_QUERY, "max_path_waypoints", 2),
+ ])
+ def test_produce_plot_limits_are_configurable(self, query, setting, value):
+ client = self.app.test_client()
+ # the query uses exactly the limit
+ with mock.patch.object(mslib.mswms.wms.mswms_settings, setting, value):
+ result = client.get('/?{}'.format(query))
+ assert b"ServiceExceptionReport" not in result.data, result.data
+ with mock.patch.object(mslib.mswms.wms.mswms_settings, setting, value - 1):
+ result = client.get('/?{}'.format(query))
+ callback_ok_xml(result.status, result.headers)
+ assert b"ServiceExceptionReport" in result.data, result.data
+
+ def test_produce_plot_accepts_limits(self):
+ # msui sends integers, but other clients may send WIDTH/HEIGHT as float
+ result = self.app.test_client().get('/?{}'.format(
+ HSEC_QUERY.replace("width=479", "width=479.0").replace("height=376", "height=")))
+ callback_ok_image(result.status, result.headers)
+ result = self.app.test_client().get('/?{}'.format(VSEC_QUERY.replace("bbox=201", "bbox=2")))
+ callback_ok_image(result.status, result.headers)
+ result = self.app.test_client().get('/?{}'.format(
+ HSEC_QUERY.replace("width=479", "width=4096").replace("height=376", "height=4096")))
+ callback_ok_image(result.status, result.headers)
+
+ def test_produce_plot_linear_section_ignores_image_size(self):
+ # a linear section returns XML, it draws no image
+ result = self.app.test_client().get('/?{}&width=65000&height=nan'.format(LSEC_QUERY))
+ callback_ok_xml(result.status, result.headers)
+ assert b"ServiceExceptionReport" not in result.data, result.data
+
+ def test_get_capabilities_advertises_limits(self):
+ result = self.app.test_client().get('/?request=GetCapabilities&service=WMS&version=1.3.0')
+ callback_ok_xml(result.status, result.headers)
+ assert b"10" in result.data
+ assert b"4096" in result.data
+ assert b"4096" in result.data
+
@pytest.mark.skip(reason="disabled because of reload")
def test_import_error(self):
with mock.patch.dict("sys.modules", {"mswms_settings": None, "mswms_auth": None}):