Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pywps/inout/formats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ def json(self, jsonin):
FORMATS = _FORMATS(
Format('application/vnd.geo+json', extension='.geojson'),
Format('application/json', extension='.json'),
Format('application/x-zipped-shp', extension='.zip'),
Format('application/x-zipped-shp', extension='.zip', encoding='base64'),
Format('application/gml+xml', extension='.gml'),
Format('application/vnd.google-earth.kml+xml', extension='.kml'),
Format('application/vnd.google-earth.kmz', extension='.kmz'),
Format('image/tiff; subtype=geotiff', extension='.tiff'),
Format('application/vnd.google-earth.kmz', extension='.kmz', encoding='base64'),
Format('image/tiff; subtype=geotiff', extension='.tiff', encoding='base64'),
Format('application/x-ogc-wcs', extension='.xml'),
Format('application/x-ogc-wcs; version=1.0.0', extension='.xml'),
Format('application/x-ogc-wcs; version=1.1.0', extension='.xml'),
Expand Down
25 changes: 15 additions & 10 deletions pywps/inout/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,25 @@ def _json_data(self, data):

data["type"] = "complex"

try:
data_doc = etree.parse(self.file)
data["data"] = etree.tostring(data_doc, pretty_print=True).decode("utf-8")
except Exception:
if self.data:

if self.data:
if isinstance(self.data, six.string_types):
if self.data_format.mime_type in ["application/xml", "application/gml+xml", "text/xml"]:
# Note that in a client-server round trip, the original and returned file will not be identical.
data_doc = etree.parse(self.file)
data["data"] = etree.tostring(data_doc, pretty_print=True).decode('utf-8')

else:
if self.data_format.encoding == 'base64':
data["data"] = self.base64.decode('utf-8')

else:
# Otherwise we assume all other formats are unsafe and need to be enclosed in a CDATA tag.
if isinstance(self.data, bytes):
data["data"] = self.data.decode("utf-8")
out = self.data.encode(self.data_format.encoding or 'utf-8')
else:
data["data"] = self.data
out = self.data

else:
data["data"] = etree.tostring(etree.CDATA(self.base64))
data["data"] = u'<![CDATA[{}]]>'.format(out)

return data

Expand Down
30 changes: 13 additions & 17 deletions pywps/inout/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,29 +123,25 @@ def _json_data(self, data):

data["type"] = "complex"

try:
data_doc = etree.parse(self.file)
data["data"] = etree.tostring(data_doc, pretty_print=True).decode("utf-8")
except Exception:

if self.data:
# XML compatible formats don't have to be wrapped in a CDATA tag.
if self.data_format.mime_type in ["application/xml", "application/gml+xml", "text/xml"]:
fmt = "{}"
else:
fmt = "<![CDATA[{}]]>"
if self.data:

if self.data_format.mime_type in ["application/xml", "application/gml+xml", "text/xml"]:
# Note that in a client-server round trip, the original and returned file will not be identical.
data_doc = etree.parse(self.file)
data["data"] = etree.tostring(data_doc, pretty_print=True).decode('utf-8')

else:
if self.data_format.encoding == 'base64':
data["data"] = fmt.format(etree.CDATA(self.base64))
data["data"] = self.base64.decode('utf-8')

elif isinstance(self.data, six.string_types):
else:
# Otherwise we assume all other formats are unsafe and need to be enclosed in a CDATA tag.
if isinstance(self.data, bytes):
data["data"] = fmt.format(self.data.decode("utf-8"))
out = self.data.encode(self.data_format.encoding or 'utf-8')
else:
data["data"] = fmt.format(self.data)
out = self.data

else:
raise NotImplementedError
data["data"] = u'<![CDATA[{}]]>'.format(out)

return data

Expand Down
2 changes: 1 addition & 1 deletion pywps/templates/1.0.0/execute/main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<ows:Title>{{ input.title }}</ows:Title>
{% if input.type == "complex" %}
<wps:Data>
<wps:ComplexData mimeType="{{ input.mimetype }}" encoding="{{ input.encoding }}" schema="{{ input.schema }}">{{ input.data }}</wps:ComplexData>
<wps:ComplexData mimeType="{{ input.mimetype }}" encoding="{{ input.encoding }}" schema="{{ input.schema }}">{{ input.data | safe }}</wps:ComplexData>
</wps:Data>
{% elif input.type == "literal" %}
<wps:Data>
Expand Down
1 change: 1 addition & 0 deletions tests/data/text/unsafe.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
< Bunch of characters that would break XML <> & "" '
139 changes: 139 additions & 0 deletions tests/test_complexdata_io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Test embedding different file formats and different encodings within the <Data> tag."""

import unittest
import os
from pywps import get_ElementMakerForVersion, E
from pywps.app.basic import get_xpath_ns
from pywps import Service, Process, ComplexInput, ComplexOutput, FORMATS
from pywps.tests import client_for, assert_response_success
from owslib.wps import WPSExecution, ComplexDataInput
from lxml import etree

VERSION = "1.0.0"
WPS, OWS = get_ElementMakerForVersion(VERSION)
xpath_ns = get_xpath_ns(VERSION)


def get_resource(path):
return os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data', path)


test_fmts = {'json': (get_resource('json/point.geojson'), FORMATS.JSON),
'geojson': (get_resource('json/point.geojson'), FORMATS.GEOJSON),
'netcdf': (get_resource('netcdf/time.nc'), FORMATS.NETCDF),
'geotiff': (get_resource('geotiff/dem.tiff'), FORMATS.GEOTIFF),
'gml': (get_resource('gml/point.gml'), FORMATS.GML),
'shp': (get_resource('shp/point.shp.zip'), FORMATS.SHP),
'txt': (get_resource('text/unsafe.txt'), FORMATS.TEXT),
}


def create_fmt_process(name, fn, fmt):
"""Create a dummy process comparing the input file on disk and the data that was passed in the request."""
def handler(request, response):
# Load output from file and convert to data
response.outputs['complex'].file = fn
o = response.outputs['complex'].data

# Get input data from the request
i = request.inputs['complex'][0].data

assert i == o
return response

return Process(handler=handler,
identifier='test-fmt',
title='Complex fmt test process',
inputs=[ComplexInput('complex', 'Complex input',
supported_formats=(fmt, ))],
outputs=[ComplexOutput('complex', 'Complex output',
supported_formats=(fmt, ))])


def get_data(fn, encoding=None):
"""Read the data from file and encode."""
import base64
mode = 'rb' if encoding == 'base64' else 'r'
with open(fn, mode) as fp:
data = fp.read()

if encoding == 'base64':
data = base64.b64encode(data)

if isinstance(data, bytes):
return data.decode('utf-8')
else:
return data


class RawInput(unittest.TestCase):

def make_request(self, name, fn, fmt):
"""Create XML request embedding encoded data."""
data = get_data(fn, fmt.encoding)

doc = WPS.Execute(
OWS.Identifier('test-fmt'),
WPS.DataInputs(
WPS.Input(
OWS.Identifier('complex'),
WPS.Data(
WPS.ComplexData(data, mimeType=fmt.mime_type, encoding=fmt.encoding)))),
version='1.0.0')

return doc

def compare_io(self, name, fn, fmt):
"""Start the dummy process, post the request and check the response matches the input data."""

# Note that `WPSRequest` calls `get_inputs_from_xml` which converts base64 input to bytes
# See `_get_rawvalue_value`
client = client_for(Service(processes=[create_fmt_process(name, fn, fmt)]))
data = get_data(fn, fmt.encoding)

wps = WPSExecution()
doc = wps.buildRequest('test-fmt',
inputs=[('complex', ComplexDataInput(data, mimeType=fmt.mime_type,
encoding=fmt.encoding))],
mode='sync')
resp = client.post_xml(doc=doc)
assert_response_success(resp)
wps.parseResponse(resp.xml)
out = wps.processOutputs[0].data[0]

if 'gml' in fmt.mime_type:
xml_orig = etree.tostring(etree.fromstring(data.encode('utf-8'))).decode('utf-8')
xml_out = etree.tostring(etree.fromstring(out.decode('utf-8'))).decode('utf-8')
# Not equal because the output includes additional namespaces compared to the origin.
# self.assertEqual(xml_out, xml_orig)

else:
self.assertEqual(out.strip(), data.strip())

def test_json(self):
key = 'json'
self.compare_io(key, *test_fmts[key])

def test_geojson(self):
key = 'geojson'
self.compare_io(key, *test_fmts[key])

def test_geotiff(self):
key = 'geotiff'
self.compare_io(key, *test_fmts[key])

def test_netcdf(self):
key = 'netcdf'
self.compare_io(key, *test_fmts[key])

def test_gml(self):
key = 'gml'
self.compare_io(key, *test_fmts[key])

def test_shp(self):
key = 'shp'
self.compare_io(key, *test_fmts[key])

def test_txt(self):
key = 'txt'
self.compare_io(key, *test_fmts[key])
21 changes: 19 additions & 2 deletions tests/test_execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,29 @@ def _handler(request, response):


def get_output(doc):
"""Return the content of LiteralData, Reference or ComplexData."""

output = {}
for output_el in xpath_ns(doc, '/wps:ExecuteResponse'
'/wps:ProcessOutputs/wps:Output'):
[identifier_el] = xpath_ns(output_el, './ows:Identifier')
[value_el] = xpath_ns(output_el, './wps:Data/wps:LiteralData')
output[identifier_el.text] = value_el.text

lit_el = xpath_ns(output_el, './wps:Data/wps:LiteralData')
if lit_el != []:
output[identifier_el.text] = lit_el[0].text

ref_el = xpath_ns(output_el, './wps:Reference')
if ref_el != []:
output[identifier_el.text] = ref_el[0].attrib['href']

data_el = xpath_ns(output_el, './wps:Data/wps:ComplexData')
if data_el != []:
if data_el[0].text:
output[identifier_el.text] = data_el[0].text
else: # XML children
ch = list(data_el[0])[0]
output[identifier_el.text] = lxml.etree.tostring(ch)

return output


Expand Down