Skip to content

Commit 373d682

Browse files
authored
Raise an error if GetCapabilities requests don't return XML (#861)
* Raise an error if responses aren't in XML * Updates * Update all service calls to use util function and add tests * Allow multiple mime types * Add another xml type * Lint fixes * Add pytest
1 parent d817969 commit 373d682

File tree

8 files changed

+78
-15
lines changed

8 files changed

+78
-15
lines changed

owslib/coverage/wcsBase.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from urllib.parse import urlencode, parse_qsl
1313
from owslib.etree import etree
14-
from owslib.util import Authentication, openURL
14+
from owslib.util import Authentication, openURL, getXMLTree
1515

1616

1717
class ServiceException(Exception):
@@ -118,7 +118,7 @@ def read(self, service_url, timeout=30):
118118
"""
119119
request = self.capabilities_url(service_url)
120120
u = openURL(request, timeout=timeout, cookies=self.cookies, auth=self.auth, headers=self.headers)
121-
return etree.fromstring(u.read())
121+
return getXMLTree(u)
122122

123123
def readString(self, st):
124124
"""Parse a WCS capabilities document, returning an

owslib/feature/common.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from owslib.etree import etree
2-
from owslib.util import Authentication, openURL
2+
from owslib.util import Authentication, openURL, getXMLTree
33

44
from urllib.parse import urlencode, parse_qsl
55

@@ -52,7 +52,7 @@ def read(self, url, timeout=30):
5252
"""
5353
request = self.capabilities_url(url)
5454
u = openURL(request, timeout=timeout, headers=self.headers, auth=self.auth)
55-
return etree.fromstring(u.read())
55+
return getXMLTree(u)
5656

5757
def readString(self, st):
5858
"""Parse a WFS capabilities document, returning an

owslib/map/common.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from urllib.parse import urlencode, parse_qsl
22

33
from owslib.etree import etree
4-
from owslib.util import strip_bom, Authentication, openURL
4+
from owslib.util import strip_bom, Authentication, openURL, getXMLTree
55

66

77
class WMSCapabilitiesReader(object):
@@ -64,9 +64,7 @@ def read(self, service_url, timeout=30):
6464
spliturl = self.request.split('?')
6565
u = openURL(spliturl[0], spliturl[1], method='Get',
6666
timeout=timeout, headers=self.headers, auth=self.auth)
67-
68-
raw_text = strip_bom(u.read())
69-
return etree.fromstring(raw_text)
67+
return getXMLTree(u)
7068

7169
def readString(self, st):
7270
"""Parse a WMS capabilities document, returning an elementtree instance.

owslib/swe/observation/sos100.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from owslib import ows
55
from owslib.crs import Crs
66
from owslib.fes import FilterCapabilities
7-
from owslib.util import openURL, testXMLValue, nspath_eval, nspath, extract_time
7+
from owslib.util import openURL, testXMLValue, nspath_eval, nspath, extract_time, getXMLTree
88
from owslib.namespaces import Namespaces
99

1010

@@ -314,7 +314,7 @@ def read(self, service_url):
314314
getcaprequest = self.capabilities_url(service_url)
315315
spliturl = getcaprequest.split('?')
316316
u = openURL(spliturl[0], spliturl[1], method='Get', username=self.username, password=self.password)
317-
return etree.fromstring(u.read())
317+
return getXMLTree(u)
318318

319319
def read_string(self, st):
320320
"""

owslib/swe/observation/sos200.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from owslib import ows
44
from owslib.crs import Crs
55
from owslib.fes2 import FilterCapabilities
6-
from owslib.util import openURL, testXMLValue, testXMLAttribute, nspath_eval, extract_time
6+
from owslib.util import openURL, testXMLValue, testXMLAttribute, nspath_eval, extract_time, getXMLTree
77
from owslib.namespaces import Namespaces
88
from owslib.swe.observation.om import MeasurementObservation
99
from owslib.swe.observation.waterml2 import MeasurementTimeseriesObservation
@@ -331,7 +331,7 @@ def read(self, service_url):
331331
getcaprequest = self.capabilities_url(service_url)
332332
spliturl = getcaprequest.split('?')
333333
u = openURL(spliturl[0], spliturl[1], method='Get', username=self.username, password=self.password)
334-
return etree.fromstring(u.read())
334+
return getXMLTree(u)
335335

336336
def read_string(self, st):
337337
"""

owslib/util.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,40 @@ def getXMLInteger(elem, tag):
325325
return int(e.text.strip())
326326

327327

328+
def getXMLTree(rsp: ResponseWrapper) -> etree:
329+
"""
330+
Parse a response into an XML elementtree instance
331+
and raise a ValueError if the server returns a
332+
non-XML response. The response may contain a useful
333+
error message from the server.
334+
335+
Parameters
336+
----------
337+
338+
@param rsp: the ResponseWrapper for the XML request
339+
"""
340+
341+
raw_text = strip_bom(rsp.read())
342+
et = etree.fromstring(raw_text)
343+
344+
# check for response type - if it is not xml then raise an error
345+
content_type = rsp.info()['Content-Type']
346+
url = rsp.geturl()
347+
348+
xml_types = ['text/xml', 'application/xml', 'application/vnd.ogc.wms_xml']
349+
if not any(xt in content_type.lower() for xt in xml_types):
350+
html_body = et.find('BODY') # note this is case-sensitive
351+
if html_body is not None and len(html_body.text) > 0:
352+
response_text = html_body.text.strip("\n")
353+
else:
354+
response_text = raw_text
355+
356+
raise ValueError("%s responded with Content-Type '%s': '%s'" %
357+
(url, content_type, response_text))
358+
359+
return et
360+
361+
328362
def testXMLValue(val, attrib=False):
329363
"""
330364

owslib/wmts.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from urllib.parse import (urlencode, urlparse, urlunparse, parse_qs,
3535
ParseResult)
3636
from .etree import etree
37-
from .util import clean_ows_url, testXMLValue, getXMLInteger, Authentication, openURL
37+
from .util import clean_ows_url, testXMLValue, getXMLInteger, Authentication, openURL, getXMLTree
3838
from .fgdc import Metadata
3939
from .iso import MD_Metadata
4040
from .ows import ServiceProvider, ServiceIdentification, OperationsMetadata
@@ -933,7 +933,7 @@ def read(self, service_url, vendor_kwargs=None):
933933
# now split it up again to use the generic openURL function...
934934
spliturl = getcaprequest.split('?')
935935
u = openURL(spliturl[0], spliturl[1], method='Get', headers=self.headers, auth=self.auth)
936-
return etree.fromstring(u.read())
936+
return getXMLTree(u)
937937

938938
def readString(self, st):
939939
"""Parse a WMTS capabilities document, returning an elementtree instance

tests/test_util.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# -*- coding: UTF-8 -*-
22
import codecs
3-
from owslib.util import clean_ows_url, build_get_url, strip_bom, extract_time
3+
from unittest import mock
4+
import pytest
5+
from owslib.util import clean_ows_url, build_get_url, strip_bom, extract_time, ResponseWrapper, getXMLTree
46
from owslib.etree import etree
57
from datetime import datetime, timezone
68

@@ -56,6 +58,35 @@ def test_build_get_url_overwrite():
5658
'http://example.org/ows?SERVICE=WMS'
5759

5860

61+
def test_getXMLTree_valid():
62+
63+
mock_resp = mock.Mock()
64+
mock_resp.url = 'http:///example.org/?service=WFS&request=GetCapabilities&version=2.0.0'
65+
mock_resp.content = b'<?xml version="1.0" encoding="UTF-8"?>\n<WFS_Capabilities><ServiceIdentification>' \
66+
b'<Title>Example</Title></ServiceIdentification></WFS_Capabilities>'
67+
mock_resp.headers = {'Content-Type': 'text/xml; charset=UTF-8'}
68+
resp_wrap = ResponseWrapper(mock_resp)
69+
70+
et = getXMLTree(resp_wrap)
71+
assert et.find('.//Title').text == "Example"
72+
73+
74+
def test_getXMLTree_invalid():
75+
76+
mock_resp = mock.Mock()
77+
mock_resp.url = 'http:///example.org/?service=WFS&request=GetCapabilities&version=2.0.0'
78+
mock_resp.content = b'<HTML><HEAD></HEAD><BODY BGCOLOR="#FFFFFF">\nmsCGILoadMap(): Web application error. ' \
79+
b'CGI variable &quot;map&quot; is not set.\n</BODY></HTML>'
80+
mock_resp.headers = {'Content-Type': 'text/html'}
81+
resp_wrap = ResponseWrapper(mock_resp)
82+
83+
with pytest.raises(ValueError) as ex:
84+
getXMLTree(resp_wrap)
85+
86+
assert str(ex.value) == 'http:///example.org/?service=WFS&request=GetCapabilities&version=2.0.0 responded with Content-Type \'text/html\'' \
87+
': \'msCGILoadMap(): Web application error. CGI variable \"map\" is not set.\''
88+
89+
5990
def test_time_zone_utc():
6091
now = datetime.utcnow()
6192
as_utc = now.replace(tzinfo=timezone.utc)

0 commit comments

Comments
 (0)