예제 #1
0
 def do_GET(self):
     parsed_path = urlparse.urlparse(self.path)
     s = QgsServer()
     headers, body = s.handleRequest(parsed_path.query)
     self.send_response(200)
     for k, v in [h.split(':') for h in headers.split('\n') if h]:
         self.send_header(k, v)
     self.end_headers()
     self.wfile.write(body)
     return
예제 #2
0
        def __init__(self) -> None:

            self.cachedir = request.config.rootdir.join('__cadastre___')
            self.datapath = request.config.rootdir.join('data')

            os.environ['QGIS_CADASTRE_CACHE_DIR'] = self.cachedir.strpath

            self.server = QgsServer()

            # Load plugins
            load_plugins(self.server.serverInterface())
예제 #3
0
 def setUp(self):
     """Create the server instance"""
     self.testdata_path = unitTestDataPath('qgis_server') + '/'
     # Clean env just to be sure
     env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
     for ev in env_vars:
         try:
             del os.environ[ev]
         except KeyError:
             pass
     self.server = QgsServer()
예제 #4
0
    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()
예제 #5
0
    class _Client:

        def __init__(self) -> None:

            self.cachedir = request.config.rootdir.join('__cadastre___')
            self.datapath = request.config.rootdir.join('data')

            os.environ['QGIS_CADASTRE_CACHE_DIR'] = self.cachedir.strpath

            self.server = QgsServer()

            # Load plugins
            load_plugins(self.server.serverInterface())

        def getplugin(self, name) -> Any:
            """ retourne l'instance du plugin
            """
            return server_plugins.get(name)

        def getprojectpath(self, name: str) -> str:
            return self.datapath.join(name)

        def get(self, query: str, project: str=None) -> _Response:
            """ Return server response from query
            """
            request  = QgsBufferServerRequest(query, QgsServerRequest.GetMethod, {}, None)
            response = QgsBufferServerResponse()
            if project is not None and not os.path.isabs(project):
                projectpath = self.datapath.join(project)
                qgsproject  = QgsProject()
                if not qgsproject.read(projectpath.strpath):
                    raise ValueError("Error reading project '%s':" % projectpath.strpath)
            else:
                qgsproject = None
            self.server.handleRequest(request, response, project=qgsproject)
            return _Response(response)
예제 #6
0
    def test_configpath(self):
        """ Test plugin can read confif path
        """
        try:
            from qgis.server import QgsServerFilter
            from qgis.core import QgsProject
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")
        self.server = QgsServer()

        # global to be modified inside plugin filters
        globals()['configFilePath2'] = None

        class Filter0(QgsServerFilter):
            """Body setter, clear body, keep headers"""

            def requestReady(self):
                global configFilePath2
                configFilePath2 = self.serverInterface().configFilePath()

        serverIface = self.server.serverInterface()
        serverIface.registerFilter(Filter0(serverIface), 100)

        # Test using MAP
        self._execute_request('?service=simple&MAP=%s' % self.projectPath)

        # Check config file path
        self.assertEqual(configFilePath2, self.projectPath)

        # Reset result
        globals()['configFilePath2'] = None

        # Test with prqject as argument
        project = QgsProject()
        project.read(self.projectPath)

        self._execute_request_project('?service=simple', project=project)

        # Check config file path
        self.assertEqual(configFilePath2, project.fileName())
예제 #7
0
    def setUp(self):
        """Create the server instance"""
        self.fontFamily = QgsFontUtils.standardTestFontFamily()
        QgsFontUtils.loadStandardTestFonts(['All'])

        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")
        self.projectAnnotationPath = os.path.join(d, "project_with_annotations.qgs")
        self.projectStatePath = os.path.join(d, "project_state.qgs")
        self.projectUseLayerIdsPath = os.path.join(d, "project_use_layerids.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()
예제 #8
0
class TestModules(unittest.TestCase):
    """
    """

    @classmethod
    def setUpClass(cls):
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def test_modules(self):
        """ Tests that modules are loaded """

        # Check that our 'SampleService is registered
        iface = self.server.serverInterface()
        service = iface.serviceRegistry().getService('SampleService')

        self.assertIsNotNone(service)
예제 #9
0
class TestQgsServer(unittest.TestCase):

    def assertXMLEqual(self, response, expected, msg=''):
        """Compare XML line by line and sorted attributes"""
        response_lines = response.splitlines()
        expected_lines = expected.splitlines()
        line_no = 1
        for expected_line in expected_lines:
            expected_line = expected_line.strip()
            response_line = response_lines[line_no - 1].strip()
            # Compare tag
            try:
                self.assertEqual(re.findall(b'<([^>\s]+)[ >]', expected_line)[0],
                                 re.findall(b'<([^>\s]+)[ >]', response_line)[0], msg=msg + "\nTag mismatch on line %s: %s != %s" % (line_no, expected_line, response_line))
            except IndexError:
                self.assertEqual(expected_line, response_line, msg=msg + "\nTag line mismatch %s: %s != %s" % (line_no, expected_line, response_line))
            #print("---->%s\t%s == %s" % (line_no, expected_line, response_line))
            # Compare attributes
            if re.match(RE_ATTRIBUTES, expected_line): # has attrs
                expected_attrs = sorted(re.findall(RE_ATTRIBUTES, expected_line))
                response_attrs = sorted(re.findall(RE_ATTRIBUTES, response_line))
                self.assertEqual(expected_attrs, response_attrs, msg=msg + "\nXML attributes differ at line {0}: {1} != {2}".format(line_no, expected_attrs, response_attrs))
            line_no += 1

    @classmethod
    def setUpClass(cls):
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def strip_version_xmlns(self, text):
        """Order of attributes is random, strip version and xmlns"""
        return text.replace(b'version="1.3.0"', b'').replace(b'xmlns="http://www.opengis.net/ogc"', b'')

    def assert_headers(self, header, body):
        stream = StringIO()
        header_string = header.decode('utf-8')
        stream.write(header_string)
        headers = email.message_from_string(header_string)
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(content_length, body_length, msg="Header reported content-length: %d Actual body length was: %d" % (content_length, body_length))

    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server

    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest("")

    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        header, body = [_v for _v in self.server.handleRequest("")]
        response = self.strip_version_xmlns(header + body)
        expected = self.strip_version_xmlns(b'Content-Length: 54\nContent-Type: text/xml; charset=utf-8\n\n<ServerException>Project file error</ServerException>\n')
        self.assertEqual(response, expected)
        expected = b'Content-Length: 54\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)

        # Test response when project is specified but without service
        project = self.testdata_path + "test_project_wfs.qgs"
        qs = 'MAP=%s' % (urllib.parse.quote(project))
        header, body = [_v for _v in self.server.handleRequest(qs)]
        response = self.strip_version_xmlns(header + body)
        expected = self.strip_version_xmlns(b'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n')
        self.assertEqual(response, expected)
        expected = b'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)

        # Test body
        expected = self.strip_version_xmlns(b'<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n')
        self.assertEqual(self.strip_version_xmlns(body), expected)

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):

            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clear()
                    request.setHeader('Content-type', 'text/plain')
                    request.appendBody('Hello from SimpleServer!'.encode('utf-8'))

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # Register some more filters
        class Filter1(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!'.encode('utf-8'))

        class Filter2(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!'.encode('utf-8'))

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Test that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

    # WMS tests
    def wms_request_compare(self, request, extra=None, reference_file=None):
        project = self.testdata_path + "test_project.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (urllib.parse.quote(project), request)
        if extra is not None:
            query_string += extra
        header, body = self.server.handleRequest(query_string)
        response = header + body
        reference_path = self.testdata_path + (request.lower() if not reference_file else reference_file) + '.txt'
        f = open(reference_path, 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(reference_path, 'wb+')
        f.write(response)
        f.close()

        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        #"""
        response = re.sub(RE_STRIP_UNCHECKABLE, b'*****', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'*****', expected)

        self.assertXMLEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected.decode('utf-8'), response.decode('utf-8')))

    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)

        # Test getfeatureinfo response
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'info_format=text%2Fhtml&transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-html')

        # Test getfeatureinfo default info_format
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-plain')

        # Regression for #8656
        # Mind the gap! (the space in the FILTER expression)
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&' +
                                 'INFO_FORMAT=text%2Fxml&' +
                                 'width=600&height=400&srs=EPSG%3A3857&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&' +
                                 'FEATURE_COUNT=10&FILTER=testlayer%20%C3%A8%C3%A9' + urllib.parse.quote(':"NAME" = \'two\''),
                                 'wms_getfeatureinfo_filter')

    def wms_inspire_request_compare(self, request):
        """WMS INSPIRE tests"""
        project = self.testdata_path + "test_project_inspire.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        response = header + body
        f = open(self.testdata_path + request.lower() + '_inspire.txt', 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)
        self.assertXMLEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected.decode('utf-8'), response.decode('utf-8')))

    def test_project_wms_inspire(self):
        """Test some WMS request"""
        for request in ('GetCapabilities',):
            self.wms_inspire_request_compare(request)

    # WFS tests
    def wfs_request_compare(self, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_' + request.lower() + '.txt', 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)

        self.assertXMLEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected.decode('utf-8'), response.decode('utf-8')))

    def test_project_wfs(self):
        """Test some WFS request"""
        for request in ('GetCapabilities', 'DescribeFeatureType'):
            self.wfs_request_compare(request)

    def wfs_getfeature_compare(self, requestid, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        self.result_compare(
            'wfs_getfeature_' + requestid + '.txt',
            "request %s failed.\n Query: %s" % (
                query_string,
                request,
            ),
            header, body
        )

    def test_wfs_getcapabilities_url(self):
        # empty url in project
        project = os.path.join(self.testdata_path, "test_project_without_urls.qgs")
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(project),
            "SERVICE": "WFS",
            "VERSION": "1.3.0",
            "REQUEST": "GetCapabilities",
            "STYLES": ""
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))

        for item in str(r).split("\\n"):
            if "onlineResource" in item:
                self.assertEqual("onlineResource=\"?" in item, True)

          # url well defined in project
        project = os.path.join(self.testdata_path, "test_project_with_urls.qgs")
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(project),
            "SERVICE": "WFS",
            "VERSION": "1.3.0",
            "REQUEST": "GetCapabilities",
            "STYLES": ""
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))

        for item in str(r).split("\\n"):
            if "onlineResource" in item:
                print("onlineResource: ", item)
                self.assertEqual("onlineResource=\"my_wfs_advertised_url\"" in item, True)

    def result_compare(self, file_name, error_msg_header, header, body):
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + file_name, 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)
        self.assertXMLEqual(response, expected, msg="%s\n Expected:\n%s\n\n Response:\n%s"
                            % (error_msg_header,
                                                    str(expected, errors='replace'),
                                                    str(response, errors='replace')))

    def test_getfeature(self):
        tests = []
        tests.append(('nobbox', 'GetFeature&TYPENAME=testlayer'))
        tests.append(('startindex2', 'GetFeature&TYPENAME=testlayer&STARTINDEX=2'))
        tests.append(('limit2', 'GetFeature&TYPENAME=testlayer&MAXFEATURES=2'))
        tests.append(('start1_limit1', 'GetFeature&TYPENAME=testlayer&MAXFEATURES=1&STARTINDEX=1'))

        for id, req in tests:
            self.wfs_getfeature_compare(id, req)

    def wfs_getfeature_post_compare(self, requestid, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP={}'.format(urllib.parse.quote(project))
        self.server.putenv("REQUEST_METHOD", "POST")
        self.server.putenv("REQUEST_BODY", request)
        header, body = self.server.handleRequest(query_string)
        self.server.putenv("REQUEST_METHOD", '')
        self.server.putenv("REQUEST_BODY", '')

        self.result_compare(
            'wfs_getfeature_{}.txt'.format(requestid),
            "GetFeature in POST for '{}' failed.".format(requestid),
            header, body,
        )

    def test_getfeature_post(self):
        template = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
  <wfs:Query typeName="testlayer" xmlns:feature="http://www.qgis.org/gml">
    <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
      <ogc:BBOX>
        <ogc:PropertyName>geometry</ogc:PropertyName>
        <gml:Envelope xmlns:gml="http://www.opengis.net/gml">
          <gml:lowerCorner>8 44</gml:lowerCorner>
          <gml:upperCorner>9 45</gml:upperCorner>
        </gml:Envelope>
      </ogc:BBOX>
    </ogc:Filter>
  </wfs:Query>
</wfs:GetFeature>
"""

        tests = []
        tests.append(('nobbox', template.format("")))
        tests.append(('startindex2', template.format('startIndex="2"')))
        tests.append(('limit2', template.format('maxFeatures="2"')))
        tests.append(('start1_limit1', template.format('startIndex="1" maxFeatures="1"')))

        for id, req in tests:
            self.wfs_getfeature_post_compare(id, req)

    def test_wms_getmap_basic(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Basic")

    def test_wms_getmap_transparent(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "TRANSPARENT": "TRUE"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Transparent")

    def test_wms_getmap_background(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "BGCOLOR": "green"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Background")

        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "BGCOLOR": "0x008000"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Background_Hex")

    def test_wms_getcapabilities_url(self):
        # empty url in project
        project = os.path.join(self.testdata_path, "test_project_without_urls.qgs")
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(project),
            "SERVICE": "WMS",
            "VERSION": "1.3.0",
            "REQUEST": "GetCapabilities",
            "STYLES": ""
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))

        item_found = False
        for item in str(r).split("\\n"):
            if "OnlineResource" in item:
                self.assertEqual("xlink:href=\"?" in item, True)
                item_found = True
        self.assertTrue(item_found)

        # url well defined in project
        project = os.path.join(self.testdata_path, "test_project_with_urls.qgs")
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(project),
            "SERVICE": "WMS",
            "VERSION": "1.3.0",
            "REQUEST": "GetCapabilities",
            "STYLES": ""
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))

        item_found = False
        for item in str(r).split("\\n"):
            if "OnlineResource" in item:
                self.assertEqual("xlink:href=\"my_wms_advertised_url?" in item, True)
                item_found = True
        self.assertTrue(item_found)

    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": "Hello",
            "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="Size error">The requested map size is too large</ServiceException>\n</ServiceExceptionReport>\n')
        r, h = self._result(self.server.handleRequest(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),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Hello,Country",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_LayerOrder")

    def test_wms_getmap_srs(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country,Hello",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-151.7,-38.9,51.0,78.0",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:4326"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_SRS")

    def test_wms_getmap_style(self):
        # default style
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country_Labels",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_StyleDefault")

        # custom style
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country_Labels",
            "STYLES": "custom",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_StyleCustom")

    def test_wms_getmap_filter(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country,Hello",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "FILTER": "Country:\"name\" = 'eurasia'"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Filter")

    def test_wms_getmap_selection(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country,Hello",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "SRS": "EPSG:3857",
            "SELECTION": "Country: 4"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Selection")

    def test_wms_getmap_opacities(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetMap",
            "LAYERS": "Country,Hello",
            "STYLES": "",
            "FORMAT": "image/png",
            "BBOX": "-16817707,-4710778,5696513,14587125",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "OPACITIES": "125, 50"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Opacities")

    def test_wms_getprint_basic(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetPrint",
            "TEMPLATE": "layoutA4",
            "FORMAT": "png",
            "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031",
            "map0:LAYERS": "Country,Hello",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Basic")

    def test_wms_getprint_srs(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetPrint",
            "TEMPLATE": "layoutA4",
            "FORMAT": "png",
            "map0:EXTENT": "-309.015,-133.011,312.179,133.949",
            "map0:LAYERS": "Country,Hello",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:4326"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_SRS")

    def test_wms_getprint_scale(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetPrint",
            "TEMPLATE": "layoutA4",
            "FORMAT": "png",
            "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031",
            "map0:LAYERS": "Country,Hello",
            "map0:SCALE": "36293562",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Scale")

    def test_wms_getprint_grid(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetPrint",
            "TEMPLATE": "layoutA4",
            "FORMAT": "png",
            "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031",
            "map0:LAYERS": "Country,Hello",
            "map0:GRID_INTERVAL_X": "1000000",
            "map0:GRID_INTERVAL_Y": "2000000",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Grid")

    def test_wms_getprint_rotation(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetPrint",
            "TEMPLATE": "layoutA4",
            "FORMAT": "png",
            "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031",
            "map0:LAYERS": "Country,Hello",
            "map0:ROTATION": "45",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Rotation")

    def test_wms_getprint_selection(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetPrint",
            "TEMPLATE": "layoutA4",
            "FORMAT": "png",
            "map0:EXTENT": "-33626185.498,-13032965.185,33978427.737,16020257.031",
            "map0:LAYERS": "Country,Hello",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "SELECTION": "Country: 4"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Selection")

    def test_getLegendGraphics(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': 'testlayer%20èé',
        }
        qs = '&'.join(["%s=%s" % (k, v) for k, v in parms.items()])
        h, r = self.server.handleRequest(qs)
        self.assertEqual(-1, h.find(b'Content-Type: text/xml; charset=utf-8'), "Header: %s\nResponse:\n%s" % (h, r))
        self.assertNotEqual(-1, h.find(b'Content-Type: image/png'), "Header: %s\nResponse:\n%s" % (h, r))

    def test_getLegendGraphics_layertitle(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer%20èé',
            'LAYERTITLE': 'TRUE',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.items()])
        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_test", 250, QSize(10, 10))

        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer%20èé',
            'LAYERTITLE': 'FALSE',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.items()])
        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_test_layertitle_false", 250, QSize(10, 10))

    def test_wms_GetLegendGraphic_Basic(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_Basic")

    def test_wms_GetLegendGraphic_Transparent(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "TRANSPARENT": "TRUE"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_Transparent")

    def test_wms_GetLegendGraphic_Background(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "BGCOLOR": "green"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_Background")

        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857",
            "BGCOLOR": "0x008000"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_Background_Hex")

    def test_wms_GetLegendGraphic_BoxSpace(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "BOXSPACE": "100",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_BoxSpace")

    def test_wms_GetLegendGraphic_SymbolSpace(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "SYMBOLSPACE": "100",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_SymbolSpace")

    def test_wms_GetLegendGraphic_IconLabelSpace(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "ICONLABELSPACE": "100",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_IconLabelSpace")

    def test_wms_GetLegendGraphic_SymbolSize(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello",
            "LAYERTITLE": "FALSE",
            "SYMBOLWIDTH": "50",
            "SYMBOLHEIGHT": "30",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "CRS": "EPSG:3857"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_SymbolSize")

    def test_wms_GetLegendGraphic_BBox(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello,db_point",
            "LAYERTITLE": "FALSE",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "BBOX": "-151.7,-38.9,51.0,78.0",
            "CRS": "EPSG:4326"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_BBox")

    def test_wms_GetLegendGraphic_BBox2(self):
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(self.projectPath),
            "SERVICE": "WMS",
            "VERSION": "1.1.1",
            "REQUEST": "GetLegendGraphic",
            "LAYER": "Country,Hello,db_point",
            "LAYERTITLE": "FALSE",
            "FORMAT": "image/png",
            "HEIGHT": "500",
            "WIDTH": "500",
            "BBOX": "-76.08,-6.4,-19.38,38.04",
            "SRS": "EPSG:4326"
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_BBox2")

    def test_wcs_getcapabilities_url(self):
        # empty url in project
        project = os.path.join(self.testdata_path, "test_project_without_urls.qgs")
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(project),
            "SERVICE": "WCS",
            "VERSION": "1.3.0",
            "REQUEST": "GetCapabilities",
            "STYLES": ""
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))

        item_found = False
        for item in str(r).split("\\n"):
            if "OnlineResource" in item:
                self.assertEqual("=\"?" in item, True)
                item_found = True
        self.assertTrue(item_found)

        # url well defined in project
        project = os.path.join(self.testdata_path, "test_project_with_urls.qgs")
        qs = "&".join(["%s=%s" % i for i in list({
            "MAP": urllib.parse.quote(project),
            "SERVICE": "WCS",
            "VERSION": "1.3.0",
            "REQUEST": "GetCapabilities",
            "STYLES": ""
        }.items())])

        r, h = self._result(self.server.handleRequest(qs))

        item_found = False
        for item in str(r).split("\\n"):
            if "OnlineResource" in item:
                print("OnlineResource: ", item)
                self.assertEqual("\"my_wcs_advertised_url" in item, True)
                item_found = True
        self.assertTrue(item_found)

    def _result(self, data):
        headers = {}
        for line in data[0].decode('UTF-8').split("\n"):
            if line != "":
                header = line.split(":")
                self.assertEqual(len(header), 2, line)
                headers[str(header[0])] = str(header[1]).strip()

        return data[1], headers

    def _img_diff(self, image, control_image, max_diff, max_size_diff=QSize()):
        temp_image = os.path.join(tempfile.gettempdir(), "%s_result.png" % control_image)

        with open(temp_image, "wb") as f:
            f.write(image)

        control = QgsRenderChecker()
        control.setControlPathPrefix("qgis_server")
        control.setControlName(control_image)
        control.setRenderedImage(temp_image)
        if max_size_diff.isValid():
            control.setSizeTolerance(max_size_diff.width(), max_size_diff.height())
        return control.compareImages(control_image), control.report()

    def _img_diff_error(self, response, headers, image, max_diff=10, max_size_diff=QSize()):
        self.assertEqual(
            headers.get("Content-Type"), "image/png",
            "Content type is wrong: %s" % headers.get("Content-Type"))
        test, report = self._img_diff(response, image, max_diff, max_size_diff)

        with open(os.path.join(tempfile.gettempdir(), image + "_result.png"), "rb") as rendered_file:
            encoded_rendered_file = base64.b64encode(rendered_file.read())
            message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.png" % (
                report, encoded_rendered_file.strip(), tempfile.gettempdir(), image
            )

        # If the failure is in image sizes the diff file will not exists.
        if os.path.exists(os.path.join(tempfile.gettempdir(), image + "_result_diff.png")):
            with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file:
                encoded_diff_file = base64.b64encode(diff_file.read())
                message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.png" % (
                    encoded_diff_file.strip(), tempfile.gettempdir(), image
                )

        self.assertTrue(test, message)
예제 #10
0
class TestQgsServerPlugins(unittest.TestCase):
    def assertXMLEqual(self, response, expected, msg=''):
        """Compare XML line by line and sorted attributes"""
        response_lines = response.splitlines()
        expected_lines = expected.splitlines()
        line_no = 1
        for expected_line in expected_lines:
            expected_line = expected_line.strip()
            response_line = response_lines[line_no - 1].strip()
            # Compare tag
            try:
                self.assertEqual(
                    re.findall(b'<([^>\s]+)[ >]', expected_line)[0],
                    re.findall(b'<([^>\s]+)[ >]', response_line)[0],
                    msg=msg + "\nTag mismatch on line %s: %s != %s" %
                    (line_no, expected_line, response_line))
            except IndexError:
                self.assertEqual(expected_line,
                                 response_line,
                                 msg=msg + "\nTag line mismatch %s: %s != %s" %
                                 (line_no, expected_line, response_line))
            # print("---->%s\t%s == %s" % (line_no, expected_line, response_line))
            # Compare attributes
            if re.match(RE_ATTRIBUTES, expected_line):  # has attrs
                expected_attrs = sorted(
                    re.findall(RE_ATTRIBUTES, expected_line))
                response_attrs = sorted(
                    re.findall(RE_ATTRIBUTES, response_line))
                self.assertEqual(
                    expected_attrs,
                    response_attrs,
                    msg=msg +
                    "\nXML attributes differ at line {0}: {1} != {2}".format(
                        line_no, expected_attrs, response_attrs))
            line_no += 1

    @classmethod
    def setUpClass(cls):
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def strip_version_xmlns(self, text):
        """Order of attributes is random, strip version and xmlns"""
        return text.replace(b'version="1.3.0"',
                            b'').replace(b'xmlns="http://www.opengis.net/ogc"',
                                         b'')

    def assert_headers(self, header, body):
        stream = StringIO()
        header_string = header.decode('utf-8')
        stream.write(header_string)
        headers = email.message_from_string(header_string)
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(
                content_length,
                body_length,
                msg=
                "Header reported content-length: %d Actual body length was: %d"
                % (content_length, body_length))

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):
            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clear()
                    request.setHeader('Content-type', 'text/plain')
                    request.appendBody(
                        'Hello from SimpleServer!'.encode('utf-8'))

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # global to be modified inside plugin filters
        globals()['status_code'] = 0
        # body to be checked inside plugin filters
        globals()['body2'] = None
        # headers to be checked inside plugin filters
        globals()['headers2'] = None

        # Register some more filters
        class Filter1(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!'.encode('utf-8'))

        class Filter2(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!'.encode('utf-8'))

        class Filter3(QgsServerFilter):
            """Test get and set status code"""
            def responseComplete(self):
                global status_code
                request = self.serverInterface().requestHandler()
                request.setStatusCode(999)
                status_code = request.statusCode()

        class Filter4(QgsServerFilter):
            """Body getter"""
            def responseComplete(self):
                global body2
                request = self.serverInterface().requestHandler()
                body2 = request.body()

        class Filter5(QgsServerFilter):
            """Body setter, clear body, keep headers"""
            def responseComplete(self):
                global headers2
                request = self.serverInterface().requestHandler()
                request.clearBody()
                headers2 = {k: request.header(k) for k in request.headerKeys()}
                request.appendBody('new body, new life!'.encode('utf-8'))

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        filter3 = Filter3(serverIface)
        filter4 = Filter4(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        serverIface.registerFilter(filter3, 300)
        serverIface.registerFilter(filter4, 400)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [
            _v for _v in self.server.handleRequest('?service=simple')
        ]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Check status code
        self.assertEqual(status_code, 999)

        # Check body getter from filter
        self.assertEqual(
            body2,
            b'Hello from SimpleServer!Hello from Filter1!Hello from Filter2!')

        # Check that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [
            _v for _v in self.server.handleRequest('?service=simple')
        ]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Now, re-run with body setter
        filter5 = Filter5(serverIface)
        serverIface.registerFilter(filter5, 500)
        header, body = [
            _v for _v in self.server.handleRequest('?service=simple')
        ]
        response = header + body
        expected = b'Content-Length: 19\nContent-type: text/plain\n\nnew body, new life!'
        self.assertEqual(response, expected)
        self.assertEqual(headers2, {'Content-type': 'text/plain'})
예제 #11
0
                        and QGIS_SERVER_PKI_USERNAME)

# Check if OAuth2 is enabled
QGIS_SERVER_OAUTH2_AUTH = (QGIS_SERVER_OAUTH2_CERTIFICATE is not None
                           and os.path.isfile(QGIS_SERVER_OAUTH2_CERTIFICATE)
                           and QGIS_SERVER_OAUTH2_KEY is not None
                           and os.path.isfile(QGIS_SERVER_OAUTH2_KEY)
                           and QGIS_SERVER_OAUTH2_AUTHORITY is not None
                           and os.path.isfile(QGIS_SERVER_OAUTH2_AUTHORITY)
                           and QGIS_SERVER_OAUTH2_USERNAME
                           and QGIS_SERVER_OAUTH2_PASSWORD)

HTTPS_ENABLED = QGIS_SERVER_PKI_AUTH or QGIS_SERVER_OAUTH2_AUTH

qgs_app = QgsApplication([], False)
qgs_server = QgsServer()

if QGIS_SERVER_HTTP_BASIC_AUTH:
    from qgis.server import QgsServerFilter
    import base64

    class HTTPBasicFilter(QgsServerFilter):
        def requestReady(self):
            handler = self.serverInterface().requestHandler()
            auth = self.serverInterface().requestHandler().requestHeader(
                'HTTP_AUTHORIZATION')
            if auth:
                username, password = base64.b64decode(auth[6:]).split(b':')
                if (username.decode('utf-8') == os.environ.get(
                        'QGIS_SERVER_USERNAME', 'username')
                        and password.decode('utf-8') == os.environ.get(
예제 #12
0
class TestQgsServer(unittest.TestCase):

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'
        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server

    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest()

    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        header, body = [str(_v) for _v in self.server.handleRequest()]
        response = header + body
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(response, expected)
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)
        # Test body
        expected = '<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(body, expected)

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print "QGIS Server plugins are not compiled. Skipping test"
            return

        class SimpleHelloFilter(QgsServerFilter):

            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clearHeaders()
                    request.setHeader('Content-type', 'text/plain')
                    request.clearBody()
                    request.appendBody('Hello from SimpleServer!')

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # Register some more filters
        class Filter1(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!')

        class Filter2(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!')

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Test that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

    # WMS tests
    def wms_request_compare(self, request):
        project = self.testdata_path + "test+project.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        response = header + body
        f = open(self.testdata_path + request.lower() + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)

    # WMS INSPIRE tests
    def wms_inspire_request_compare(self, request):
        project = self.testdata_path + "test+project_inspire.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        response = header + body
        f = open(self.testdata_path + request.lower() + '_inspire.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wms_inspire(self):
        """Test some WMS request"""
        for request in ('GetCapabilities',):
            self.wms_inspire_request_compare(request)

    # WFS tests
    def wfs_request_compare(self, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_' + request.lower() + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wfs(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'DescribeFeatureType'):
            self.wfs_request_compare(request)

    def wfs_getfeature_compare(self, requestid, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_getfeature_' + requestid + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg=u"request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
                                                 % (query_string,
                                                    request,
                                                    unicode(expected, errors='replace'),
                                                    unicode(response, errors='replace')))

    def test_getfeature(self):
        tests = []
        tests.append(('nobbox', u'GetFeature&TYPENAME=testlayer'))
        tests.append(('startindex2', u'GetFeature&TYPENAME=testlayer&STARTINDEX=2'))

        for id, req in tests:
            self.wfs_getfeature_compare(id, req)

    def assert_headers(self, header, body):
        headers = Message(StringIO(header))
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(content_length, body_length, msg="Header reported content-length: %d Actual body length was: %d" % (content_length, body_length))
예제 #13
0
class TestQgsServerSecurity(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.testdatapath = unitTestDataPath('qgis_server_security') + '/'
        cls.db = os.path.join(cls.testdatapath, 'db.sqlite')
        cls.db_clone = os.path.join(cls.testdatapath, 'db_clone.sqlite')
        cls.project = os.path.join(cls.testdatapath, 'project.qgs')
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()
        try:
            os.remove(cls.db_clone)
        except OSError:
            pass

    def setUp(self):
        self.server = QgsServer()
        copyfile(self.db, self.db_clone)

    def test_wms_getfeatureinfo_filter_and_based_blind(self):
        """
        And-based blind attack to check the kind of database currently used (if
        the result is valid for the point nammed 'b', then sqlite_version()
        function exist).

        But does not work because of the whitelist.

        If you remove the safety check, this is a valid injection.
        """

        filter_sql = "point:\"name\" = 'b'"
        injection_sql = ") and (select sqlite_version()"

        query = "{0} {1}".format(filter_sql, injection_sql)
        d, h = self.handle_request_wms_getfeatureinfo(query)

        self.assertFalse(b"name = 'b'" in d)

    def test_wms_getfeatureinfo_filter_time_based_blind(self):
        """
        Time-based blind to check the current version of database. If the
        server is too long to respond, then we have the answer!

        But it does not work because of the whitelist.

        If you remove the safety check, this is a valid injection.
        """

        # first step, retrieve the version of sqlite by a regular way
        conn = spatialite_connect(self.db_clone)
        cur = conn.cursor()
        sql = "select sqlite_version()"
        sqlite_version = ''
        for row in cur.execute(sql):
            sqlite_version = row[0]
        conn.close()

        # second step, check the time of response for an invalid version
        filter_sql = "point:\"name\" = 'b'"
        injection_sql = ") and (select case sqlite_version() when '0.0.0' then substr(upper(hex(randomblob(99999999))),0,1) end)--"

        query = "{0} {1}".format(filter_sql, injection_sql)
        start = time.time()
        d, h = self.handle_request_wms_getfeatureinfo(query)
        duration_invalid_version = time.time() - start

        # third step, check the time of response for a valid version
        # maximum: several seconds
        injection_sql = ") and (select case sqlite_version() when '{0}' then substr(upper(hex(randomblob(99999999))),0,1) end)--".format(
            sqlite_version)

        query = "{0} {1}".format(filter_sql, injection_sql)
        start = time.time()
        d, h = self.handle_request_wms_getfeatureinfo(query)
        duration_valid_version = time.time() - start

        # compare duration. On my computer when safety check is deactivated:
        # duration_invalid_version: 0.012360334396362305
        # duration_valid_version: 2.8810460567474365
        self.assertAlmostEqual(duration_valid_version,
                               duration_invalid_version,
                               delta=0.5)

    def test_wms_getfeatureinfo_filter_stacked(self):
        """
        The aim is to execute some staked queries. Here the 'drop' function is
        used but it could be done with create, insert, ...

        But the filter string is split thanks to the semicolon so it seems
        totally ignored whatever the query is (even without the safety check).
        """

        filter_sql = "point:\"name\" = 'fake'"
        injection_sql = "); drop table point"

        query = "{0} {1}".format(filter_sql, injection_sql)
        d, h = self.handle_request_wms_getfeatureinfo(query)

        self.assertTrue(self.is_point_table_still_exist())

    def test_wms_getfeatureinfo_filter_union_0(self):
        """
        The aim is to retrieve name of tables within the database (like
        'SpatialIndex').

        But the whitelist blocks this request because of invalid tokens.

        If you remove the safety check, this is a valid injection.
        """

        filter_sql = "point:\"name\" = 'fake'"
        injection_sql = ") union select 1,1,name,1,1 from sqlite_master where type = \"table\" order by name--"

        query = "{0} {1}".format(filter_sql, injection_sql)
        d, h = self.handle_request_wms_getfeatureinfo(query)

        self.assertFalse(b'SpatialIndex' in d)

    def test_wms_getfeatureinfo_filter_union_1(self):
        """
        The aim is to retrieve data from an excluded layer.

        But the whitelist blocks this request because of invalid tokens.

        If you remove the safety check, this is a valid injection.
        """

        filter_sql = "point:\"name\" = 'fake'"
        injection_sql = ") union select 1,1,* from aoi--"

        query = "{0} {1}".format(filter_sql, injection_sql)
        d, h = self.handle_request_wms_getfeatureinfo(query)

        self.assertFalse(b'private_value' in d)

    def test_wms_getfeatureinfo_filter_unicode(self):
        """
        The aim is to send some invalid token in unicode to bypass the
        whitelist.

        But unicode is interpreted and checked by the safety function.
        """

        # %3B -> semicolon
        filter_sql = "point:\"name\" = 'fake %3B'"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

    def test_wms_getfeatureinfo_filter_patternmatching(self):
        """
        The aim is to retrieve the table's name thanks to pattern matching.

        If you remove the safety check, this is a valid injection.
        """

        filter_sql = "point:\"name\" = 'b'"
        injection_sql = "or ( select name from sqlite_master where type='table' and name like '{0}') != ''"
        query = "{0} {1}".format(filter_sql, injection_sql)

        # there's no table named as 'az%'
        name = "az%"
        sql = query.format(name)
        d, h = self.handle_request_wms_getfeatureinfo(sql)
        # self.assertTrue(b"name = 'b'" in d) #true if sanity check deactivated
        self.assertTrue(self.check_service_exception_report(d))

        # a table named as 'ao%' exist
        name = "ao%"
        sql = query.format(name)
        d, h = self.handle_request_wms_getfeatureinfo(sql)
        # self.assertTrue(b"name = 'a'" in d) #true if sanity check deactivated
        self.assertTrue(self.check_service_exception_report(d))

        # a table named as 'aoi' exist
        name = "aoi"
        sql = query.format(name)
        d, h = self.handle_request_wms_getfeatureinfo(sql)
        # self.assertTrue(b"name = 'a'" in d) #true if sanity check deactivated
        self.assertTrue(self.check_service_exception_report(d))

    def test_wms_getfeatureinfo_filter_whitelist(self):
        """
        The aim is to check that some tokens cannot pass the safety check
        whatever their positions in the filter string.
        """

        # create
        filter_sql = "point:\"name\" = 'a'create"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

        filter_sql = "point:\"name\" = 'a' create"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

        # invalid token and escape single quote
        filter_sql = "point:\"name\" = 'a\\'create"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

        # drop
        filter_sql = "point:\"name\" = 'a' drop"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

        # select
        filter_sql = "point:\"name\" = 'a' select"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

        # comments
        filter_sql = "point:\"name\" = 'a' #"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

        filter_sql = "point:\"name\" = 'a' -"
        d, h = self.handle_request_wms_getfeatureinfo(filter_sql)
        self.assertTrue(self.check_service_exception_report(d))

    def test_wfs_getfeature_filter_stacked(self):
        """
        The aim is to execute some staked queries within the 'Literal'
        and 'PropertyName' field. Here the 'drop' function is used but it
        could be done with create, insert, ...

        But due to the implementation, these filters are not resolved on
        database side but in server side with QgsExpression. So, there's no
        'WHERE' clause and the query never reach the database. By the way,
        it's exactly the same thing whatever the kind of attacks and for
        the EXP_FILTER parameter too (filter described with QgsExpression).

        It's typically the kind of SQL injection which has been fixed in
        mapserver several years ago:
        https://trac.osgeo.org/mapserver/ticket/3874
        """

        # ogc:Literal / ogc:PropertyIsEqualTo
        literal = "4')); drop table point --"
        filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsEqualTo><ogc:PropertyName>pkuid</ogc:PropertyName><ogc:Literal>{0}</ogc:Literal></ogc:PropertyIsEqualTo></ogc:Filter>".format(
            literal)
        self.handle_request_wfs_getfeature_filter(filter_xml)
        self.assertTrue(self.is_point_table_still_exist())

        # ogc:Literal / ogc:PropertyIsLike
        literal = "4')); drop table point --"
        filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsLike><ogc:PropertyName>pkuid</ogc:PropertyName><ogc:Literal>{0}</ogc:Literal></ogc:PropertyIsLike></ogc:Filter>".format(
            literal)
        self.handle_request_wfs_getfeature_filter(filter_xml)
        self.assertTrue(self.is_point_table_still_exist())

        # ogc:PropertyName / ogc:PropertyIsLike
        propname = "name = 'a')); drop table point --"
        filter_xml = "<ogc:Filter%20xmlns:ogc=\"http://www.opengis.net/ogc\"><ogc:PropertyIsLike><ogc:PropertyName>{0}</ogc:PropertyName><ogc:Literal>4</ogc:Literal></ogc:PropertyIsLike></ogc:Filter>".format(
            propname)
        self.handle_request_wfs_getfeature_filter(filter_xml)
        self.assertTrue(self.is_point_table_still_exist())

    def test_wms_getmap_sld_stacked(self):
        """
        The aim is to execute some staked queries within the 'Literal'
        and 'PropertyName' field. Here the 'drop' function is used but it
        could be done with create, insert, ...

        However it's not working because special characters are duplicated. For
        example, with 'Literal' as "4')); drop table point --", the underlying
        query is:
            SELECT .... AND (("pkuid" = '4'')); drop table point --'))
        """

        literal = "4')); drop table point --"
        sld = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><StyledLayerDescriptor xmlns=\"http://www.opengis.net/sld\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:ogc=\"http://www.opengis.net/ogc\" xsi:schemaLocation=\"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.1.0/StyledLayerDescriptor.xsd\" version=\"1.1.0\" xmlns:se=\"http://www.opengis.net/se\" xmlns:xlink=\"http://www.w3.org/1999/xlink\"> <NamedLayer> <se:Name>point</se:Name> <UserStyle> <se:Name>point</se:Name> <se:FeatureTypeStyle> <se:Rule> <se:Name>Single symbol</se:Name> <ogc:Filter xmlns:ogc=\"http://www.opengis.net/ogc\"> <ogc:PropertyIsEqualTo> <ogc:PropertyName>pkuid</ogc:PropertyName> <ogc:Literal>{0}</ogc:Literal> </ogc:PropertyIsEqualTo> </ogc:Filter> <se:PointSymbolizer> <se:Graphic> <se:Mark> <se:WellKnownName>circle</se:WellKnownName> <se:Fill><se:SvgParameter name=\"fill\">5e86a1</se:SvgParameter></se:Fill><se:Stroke><se:SvgParameter name=\"stroke\">000000</se:SvgParameter></se:Stroke></se:Mark><se:Size>7</se:Size></se:Graphic></se:PointSymbolizer></se:Rule></se:FeatureTypeStyle></UserStyle></NamedLayer></StyledLayerDescriptor>".format(
            literal)
        self.handle_request_wms_getmap(sld)
        self.assertTrue(self.is_point_table_still_exist())

    def check_service_exception_report(self, d):
        """
        Return True if a ServiceExceptionReport is raised, False otherwise
        """

        if b'<ServiceExceptionReport' in d:
            return True
        else:
            return False

    def handle_request_wfs_getfeature_filter(self, filter_xml):
        qs = "&".join([
            "%s=%s" % i for i in list({
                "MAP": urllib.parse.quote(self.project),
                "SERVICE": "WFS",
                "VERSION": "1.1.1",
                "REQUEST": "GetFeature",
                "TYPENAME": "point",
                "STYLES": "",
                "CRS": "EPSG:32613",
                "FILTER": filter_xml
            }.items())
        ])

        return self.server.handleRequest(qs)

    def handle_request_wms_getfeatureinfo(self, filter_sql):
        qs = "&".join([
            "%s=%s" % i for i in list({
                "MAP": urllib.parse.quote(self.project),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetFeatureInfo",
                "QUERY_LAYERS": "point",
                "LAYERS": "point",
                "STYLES": "",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "BBOX": "606171,4822867,612834,4827375",
                "CRS": "EPSG:32613",
                "FILTER": filter_sql
            }.items())
        ])

        return self._result(self.server.handleRequest(qs))

    def handle_request_wms_getmap(self, sld):
        qs = "&".join([
            "%s=%s" % i for i in list({
                "MAP": urllib.parse.quote(self.project),
                "SERVICE": "WMS",
                "VERSION": "1.0.0",
                "REQUEST": "GetMap",
                "QUERY_LAYERS": "point",
                "LAYERS": "point",
                "STYLES": "",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "BBOX": "606171,4822867,612834,4827375",
                "CRS": "EPSG:32613",
                "SLD": sld
            }.items())
        ])

        return self._result(self.server.handleRequest(qs))

    def is_point_table_still_exist(self):
        conn = spatialite_connect(self.db_clone)
        cur = conn.cursor()
        sql = "select * from point"
        point_table_exist = True
        try:
            cur.execute(sql)
        except:
            point_table_exist = False
        conn.close()

        return point_table_exist

    def _result(self, data):
        headers = {}
        for line in data[0].decode('UTF-8').split("\n"):
            if line != "":
                header = line.split(":")
                self.assertEqual(len(header), 2, line)
                headers[str(header[0])] = str(header[1]).strip()

        return data[1], headers
예제 #14
0
class TestQgsServer(unittest.TestCase):

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'
        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def assert_headers(self, header, body):
        headers = Message(StringIO(header))
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(content_length, body_length, msg="Header reported content-length: %d Actual body length was: %d" % (content_length, body_length))

    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server

    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest()

    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        header, body = [str(_v) for _v in self.server.handleRequest()]
        response = header + body
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(response, expected)
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)
        # Test body
        expected = '<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(body, expected)

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):

            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clearHeaders()
                    request.setHeader('Content-type', 'text/plain')
                    request.clearBody()
                    request.appendBody('Hello from SimpleServer!')

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # Register some more filters
        class Filter1(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!')

        class Filter2(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!')

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Test that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

    # WMS tests
    def wms_request_compare(self, request, extra=None, reference_file=None):
        project = self.testdata_path + "test+project.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (urllib.quote(project), request)
        if extra is not None:
            query_string += extra
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        response = header + body
        f = open(self.testdata_path + (request.lower() if not reference_file else reference_file) + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace('typeName="Integer64" precision="0" length="10" editType="TextEdit" type="qlonglong"', 'typeName="Integer" precision="0" length="10" editType="TextEdit" type="int"')

        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)

        # Test getfeatureinfo response
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'info_format=text%2Fhtml&transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-html')

        # Test getfeatureinfo default info_format
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-plain')

    def wms_inspire_request_compare(self, request):
        """WMS INSPIRE tests"""
        project = self.testdata_path + "test+project_inspire.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        response = header + body
        f = open(self.testdata_path + request.lower() + '_inspire.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wms_inspire(self):
        """Test some WMS request"""
        for request in ('GetCapabilities',):
            self.wms_inspire_request_compare(request)

    # WFS tests
    def wfs_request_compare(self, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_' + request.lower() + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace('<element type="long" name="id"/>', '<element type="integer" name="id"/>')

        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wfs(self):
        """Test some WFS request"""
        for request in ('GetCapabilities', 'DescribeFeatureType'):
            self.wfs_request_compare(request)

    def wfs_getfeature_compare(self, requestid, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_getfeature_' + requestid + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg=u"request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
                                                 % (query_string,
                                                    request,
                                                    unicode(expected, errors='replace'),
                                                    unicode(response, errors='replace')))

    def test_getfeature(self):
        tests = []
        tests.append(('nobbox', u'GetFeature&TYPENAME=testlayer'))
        tests.append(('startindex2', u'GetFeature&TYPENAME=testlayer&STARTINDEX=2'))

        for id, req in tests:
            self.wfs_getfeature_compare(id, req)

    def test_getLegendGraphics(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test%2Bproject.qgs",
            'SERVICE': 'WMS',
            'VERSIONE': '1.0.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer+èé',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.iteritems()])
        h, r = self.server.handleRequest(qs)
        self.assertEqual(-1, h.find('Content-Type: text/xml; charset=utf-8'), "Header: %s\nResponse:\n%s" % (h, r))
        self.assertNotEquals(-1, h.find('Content-Type: image/png'), "Header: %s\nResponse:\n%s" % (h, r))
예제 #15
0
class QgsServerTestBase(unittest.TestCase):

    """Base class for QGIS server tests"""

    # Set to True in child classes to re-generate reference files for this class
    regenerate_reference = False

    def assertXMLEqual(self, response, expected, msg=''):
        """Compare XML line by line and sorted attributes"""
        response_lines = response.splitlines()
        expected_lines = expected.splitlines()
        line_no = 1
        self.assertEqual(len(expected_lines), len(response_lines), "Expected and response have different number of lines!\n{}".format(msg))
        for expected_line in expected_lines:
            expected_line = expected_line.strip()
            response_line = response_lines[line_no - 1].strip()
            response_line = response_line.replace(b'e+6', b'e+06')
            # Compare tag
            if re.match(RE_ELEMENT, expected_line):
                expected_elements = re.findall(RE_ELEMENT, expected_line)
                response_elements = re.findall(RE_ELEMENT, response_line)
                self.assertEqual(expected_elements[0],
                                 response_elements[0], msg=msg + "\nTag mismatch on line %s: %s != %s" % (line_no, expected_line, response_line))
                # Compare content
                if len(expected_elements) == 2 and expected_elements[0] == expected_elements[1]:
                    expected_element_content = re.findall(RE_ELEMENT_CONTENT, expected_line)
                    response_element_content = re.findall(RE_ELEMENT_CONTENT, response_line)
                    self.assertEqual(len(expected_element_content), len(response_element_content),
                                     msg=msg + "\nContent mismatch on line %s: %s != %s" % (line_no, expected_line, response_line))
                    if len(expected_element_content):
                        self.assertEqual(expected_element_content[0],
                                         response_element_content[0], msg=msg + "\nContent mismatch on line %s: %s != %s" % (line_no, expected_line, response_line))
            else:
                self.assertEqual(expected_line, response_line, msg=msg + "\nTag line mismatch %s: %s != %s\n%s" % (line_no, expected_line, response_line, msg))
            # print("---->%s\t%s == %s" % (line_no, expected_line, response_line))
            # Compare attributes
            if re.findall(RE_ATTRIBUTES, expected_line):  # has attrs
                expected_attrs, expected_values = zip(*sorted(re.findall(RE_ATTRIBUTES, expected_line)))
                self.assertTrue(re.findall(RE_ATTRIBUTES, response_line), msg=msg + "\nXML attributes differ at line {0}: {1} != {2}".format(line_no, expected_line, response_line))
                response_attrs, response_values = zip(*sorted(re.findall(RE_ATTRIBUTES, response_line)))
                self.assertEqual(expected_attrs, response_attrs, msg=msg + "\nXML attributes differ at line {0}: {1} != {2}".format(line_no, expected_attrs, response_attrs))
                self.assertEqual(expected_values, response_values, msg=msg + "\nXML attribute values differ at line {0}: {1} != {2}".format(line_no, expected_values, response_values))
            line_no += 1

    @classmethod
    def setUpClass(cls):
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()

    def setUp(self):
        """Create the server instance"""
        self.fontFamily = QgsFontUtils.standardTestFontFamily()
        QgsFontUtils.loadStandardTestFonts(['All'])

        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")
        self.projectAnnotationPath = os.path.join(d, "project_with_annotations.qgs")
        self.projectStatePath = os.path.join(d, "project_state.qgs")
        self.projectUseLayerIdsPath = os.path.join(d, "project_use_layerids.qgs")
        self.projectGroupsPath = os.path.join(d, "project_groups.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def strip_version_xmlns(self, text):
        """Order of attributes is random, strip version and xmlns"""
        return text.replace(b'version="1.3.0"', b'').replace(b'xmlns="http://www.opengis.net/ogc"', b'')

    def assert_headers(self, header, body):
        stream = StringIO()
        header_string = header.decode('utf-8')
        stream.write(header_string)
        headers = email.message_from_string(header_string)
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(content_length, body_length, msg="Header reported content-length: %d Actual body length was: %d" % (content_length, body_length))

    @classmethod
    def store_reference(self, reference_path, response):
        """Utility to store reference files"""

        # Normally this is false
        if not self.regenerate_reference:
            return

        # Store the output for debug or to regenerate the reference documents:
        f = open(reference_path, 'wb+')
        f.write(response)
        f.close()

    def _result(self, data):
        headers = {}
        for line in data[0].decode('UTF-8').split("\n"):
            if line != "":
                header = line.split(":")
                self.assertEqual(len(header), 2, line)
                headers[str(header[0])] = str(header[1]).strip()

        return data[1], headers

    def _img_diff(self, image, control_image, max_diff, max_size_diff=QSize()):
        temp_image = os.path.join(tempfile.gettempdir(), "%s_result.png" % control_image)

        with open(temp_image, "wb") as f:
            f.write(image)

        control = QgsRenderChecker()
        control.setControlPathPrefix("qgis_server")
        control.setControlName(control_image)
        control.setRenderedImage(temp_image)
        if max_size_diff.isValid():
            control.setSizeTolerance(max_size_diff.width(), max_size_diff.height())
        return control.compareImages(control_image, max_diff), control.report()

    def _img_diff_error(self, response, headers, image, max_diff=100, max_size_diff=QSize()):
        self.assertEqual(
            headers.get("Content-Type"), "image/png",
            "Content type is wrong: %s\n%s" % (headers.get("Content-Type"), response))

        test, report = self._img_diff(response, image, max_diff, max_size_diff)

        with open(os.path.join(tempfile.gettempdir(), image + "_result.png"), "rb") as rendered_file:
            encoded_rendered_file = base64.b64encode(rendered_file.read())
            if not os.environ.get('ENCODED_OUTPUT'):
                message = "Image is wrong\: rendered file %s/%s_result.png" % (tempfile.gettempdir(), image)
            else:
                message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.png" % (
                    report, encoded_rendered_file.strip().decode('utf8'), tempfile.gettempdir(), image
                )

        # If the failure is in image sizes the diff file will not exists.
        if os.path.exists(os.path.join(tempfile.gettempdir(), image + "_result_diff.png")):
            with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file:
                if not os.environ.get('ENCODED_OUTPUT'):
                    message = "Image is wrong\: diff file %s/%s_result_diff.png" % (tempfile.gettempdir(), image)
                else:
                    encoded_diff_file = base64.b64encode(diff_file.read())
                    message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.png" % (
                        encoded_diff_file.strip().decode('utf8'), tempfile.gettempdir(), image
                    )

        self.assertTrue(test, message)

    def _execute_request(self, qs, requestMethod=QgsServerRequest.GetMethod, data=None):
        request = QgsBufferServerRequest(qs, requestMethod, {}, data)
        response = QgsBufferServerResponse()
        self.server.handleRequest(request, response)
        headers = []
        rh = response.headers()
        rk = sorted(rh.keys())
        for k in rk:
            headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
        return b"\n".join(headers) + b"\n\n", bytes(response.body())

    def _execute_request_project(self, qs, project, requestMethod=QgsServerRequest.GetMethod, data=None):
        request = QgsBufferServerRequest(qs, requestMethod, {}, data)
        response = QgsBufferServerResponse()
        self.server.handleRequest(request, response, project)
        headers = []
        rh = response.headers()
        rk = sorted(rh.keys())
        for k in rk:
            headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
        return b"\n".join(headers) + b"\n\n", bytes(response.body())
예제 #16
0
            attributes.remove("colour")
        return attributes

    def allowToEdit(self, layer, feature):
        """ Are we authorise to modify the following geometry """

        if not self._active:
            return super(RestrictedAccessControl, self).allowToEdit(layer, feature)

        return feature.attribute("color") in ["red", "yellow"]

    def cacheKey(self):
        return "r" if self._active else "f"


server = QgsServer()
server.handleRequest()
server_iface = server.serverInterface()
accesscontrol = RestrictedAccessControl(server_iface)
server_iface.registerAccessControl(accesscontrol, 100)


class TestQgsServerAccessControl(TestCase):

    def setUp(self):
        self.testdata_path = unitTestDataPath("qgis_server_accesscontrol")

        dataFile = os.path.join(self.testdata_path, "helloworld.db")
        self.assertTrue(os.path.isfile(dataFile), 'Could not find data file "{}"'.format(dataFile))
        copyfile(dataFile, os.path.join(self.testdata_path, "_helloworld.db"))
예제 #17
0
class TestQgsServerPlugins(QgsServerTestBase):

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):

            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clear()
                    request.setResponseHeader('Content-type', 'text/plain')
                    request.appendBody('Hello from SimpleServer!'.encode('utf-8'))

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # global to be modified inside plugin filters
        globals()['status_code'] = 0
        # body to be checked inside plugin filters
        globals()['body2'] = None
        # headers to be checked inside plugin filters
        globals()['headers2'] = None

        # Register some more filters
        class Filter1(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!'.encode('utf-8'))

        class Filter2(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!'.encode('utf-8'))

        class Filter3(QgsServerFilter):
            """Test get and set status code"""

            def responseComplete(self):
                global status_code
                request = self.serverInterface().requestHandler()
                request.setStatusCode(999)
                status_code = request.statusCode()

        class Filter4(QgsServerFilter):
            """Body getter"""

            def responseComplete(self):
                global body2
                request = self.serverInterface().requestHandler()
                body2 = request.body()

        class Filter5(QgsServerFilter):
            """Body setter, clear body, keep headers"""

            def responseComplete(self):
                global headers2
                request = self.serverInterface().requestHandler()
                request.clearBody()
                headers2 = request.responseHeaders()
                request.appendBody('new body, new life!'.encode('utf-8'))

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        filter3 = Filter3(serverIface)
        filter4 = Filter4(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        serverIface.registerFilter(filter3, 300)
        serverIface.registerFilter(filter4, 400)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self._execute_request('?service=simple')]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Check status code
        self.assertEqual(status_code, 999)

        # Check body getter from filter
        self.assertEqual(body2, b'Hello from SimpleServer!Hello from Filter1!Hello from Filter2!')

        # Check that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self._execute_request('?service=simple')]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Now, re-run with body setter
        filter5 = Filter5(serverIface)
        serverIface.registerFilter(filter5, 500)
        header, body = [_v for _v in self._execute_request('?service=simple')]
        response = header + body
        expected = b'Content-Length: 19\nContent-type: text/plain\n\nnew body, new life!'
        self.assertEqual(response, expected)
        self.assertEqual(headers2, {'Content-type': 'text/plain'})
예제 #18
0
class TestQgsServer(unittest.TestCase):

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'
        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()


    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server


    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest()


    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        response = str(self.server.handleRequest())
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(response, expected)
        # Test header
        response = str(self.server.handleRequestGetHeaders())
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(response, expected)
        # Test body
        response = str(self.server.handleRequestGetBody())
        expected = '<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(response, expected)


    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print "QGIS Server plugins are not compiled. Skipping test"
            return

        class SimpleHelloFilter(QgsServerFilter):
            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clearHeaders()
                    request.setHeader('Content-type', 'text/plain')
                    request.clearBody()
                    request.appendBody('Hello from SimpleServer!')


        serverIface = self.server.serverInterface()
        serverIface.registerFilter(SimpleHelloFilter(serverIface), 100 )
        response = str(self.server.handleRequest('service=simple'))
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!'
        self.assertEqual(response, expected)


    ## WMS tests
    def wms_request_compare(self, request):
        map = self.testdata_path + "testproject.qgs"
        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (map, request)
        response = str(self.server.handleRequest(query_string))
        f = open(self.testdata_path + request.lower() + '.txt')
        expected = f.read()
        f.close()
        # Store for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg="request %s failed. Expected:\n%s\n\nResponse:\n%s" % (request, expected, response))


    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)
예제 #19
0
 def test_multiple_servers(self):
     """Segfaults?"""
     for i in range(10):
         locals()["s%s" % i] = QgsServer()
         locals()["s%s" % i].handleRequest()
예제 #20
0
class TestQgsServer(unittest.TestCase):
    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'
        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server

    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest()

    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        header, body = [str(_v) for _v in self.server.handleRequest()]
        response = header + body
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(response, expected)
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)
        # Test body
        expected = '<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(body, expected)

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print "QGIS Server plugins are not compiled. Skipping test"
            return

        class SimpleHelloFilter(QgsServerFilter):
            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clearHeaders()
                    request.setHeader('Content-type', 'text/plain')
                    request.clearBody()
                    request.appendBody('Hello from SimpleServer!')

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # Register some more filters
        class Filter1(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!')

        class Filter2(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!')

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [
            str(_v) for _v in self.server.handleRequest('service=simple')
        ]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Test that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [
            str(_v) for _v in self.server.handleRequest('service=simple')
        ]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

    # WMS tests
    def wms_request_compare(self, request):
        project = self.testdata_path + "test+project.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (
            urllib.quote(project), request)
        header, body = [
            str(_v) for _v in self.server.handleRequest(query_string)
        ]
        response = header + body
        f = open(self.testdata_path + request.lower() + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(
            response,
            expected,
            msg=
            "request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
            % (query_string, request, expected, response))

    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)

    # WFS tests
    def wfs_request_compare(self, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (
            urllib.quote(project), request)
        header, body = [
            str(_v) for _v in self.server.handleRequest(query_string)
        ]
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_' + request.lower() + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(
            response,
            expected,
            msg=
            "request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
            % (query_string, request, expected, response))

    def test_project_wfs(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'DescribeFeatureType'):
            self.wfs_request_compare(request)

    def wfs_getfeature_compare(self, requestid, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (
            urllib.quote(project), request)
        header, body = [
            str(_v) for _v in self.server.handleRequest(query_string)
        ]
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_getfeature_' + requestid + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(
            response,
            expected,
            msg=
            u"request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
            % (query_string, request, unicode(expected, errors='replace'),
               unicode(response, errors='replace')))

    def test_getfeature(self):
        tests = []
        tests.append(('nobbox', u'GetFeature&TYPENAME=testlayer'))
        tests.append(
            ('startindex2', u'GetFeature&TYPENAME=testlayer&STARTINDEX=2'))

        for id, req in tests:
            self.wfs_getfeature_compare(id, req)

    def assert_headers(self, header, body):
        headers = Message(StringIO(header))
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(
                content_length,
                body_length,
                msg=
                "Header reported content-length: %d Actual body length was: %d"
                % (content_length, body_length))
standard_library.install_aliases()

__author__ = 'Alessandro Pasotti'
__date__ = '05/15/2016'
__copyright__ = 'Copyright 2016, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'



QGIS_SERVER_PORT = int(os.environ.get('QGIS_SERVER_PORT', '8083'))
#QGIS_SERVER_HOST = os.environ.get('QGIS_SERVER_HOST', '127.0.0.1')
QGIS_SERVER_HOST = '' # listen an all interfaces


qgs_server = QgsServer()


QGIS_SERVER_OAUTH2_USERNAME = os.environ.get(
    'QGIS_SERVER_OAUTH2_USERNAME', 'username')
QGIS_SERVER_OAUTH2_PASSWORD = os.environ.get(
    'QGIS_SERVER_OAUTH2_PASSWORD', 'password')
QGIS_SERVER_OAUTH2_TOKEN_EXPIRES_IN = os.environ.get(
    'QGIS_SERVER_OAUTH2_TOKEN_EXPIRES_IN', 3600)


print("SERVER_NAME (from env, will be used fo urls): %s" % os.environ.get('SERVER_NAME'))
print("OAuth2 username: %s" % QGIS_SERVER_OAUTH2_USERNAME)
print("OAuth2 password: %s" % QGIS_SERVER_OAUTH2_PASSWORD)

# Naive token storage implementation
예제 #22
0
class QgsServerTestBase(unittest.TestCase):
    """Base class for QGIS server tests"""

    # Set to True in child classes to re-generate reference files for this class
    regenerate_reference = False

    def assertXMLEqual(self, response, expected, msg='', raw=False):
        """Compare XML line by line and sorted attributes"""
        response_lines = response.splitlines()
        expected_lines = expected.splitlines()
        line_no = 1

        diffs = []
        for diff in difflib.unified_diff(
            [l.decode('utf8') for l in expected_lines],
            [l.decode('utf8') for l in response_lines]):
            diffs.append(diff)

        self.assertEqual(
            len(expected_lines), len(response_lines),
            "Expected and response have different number of lines!\n{}\n{}\nWe got :\n{}"
            .format(msg, '\n'.join(diffs),
                    '\n'.join([i.decode("utf-8") for i in response_lines])))
        for expected_line in expected_lines:
            expected_line = expected_line.strip()
            response_line = response_lines[line_no - 1].strip()
            response_line = response_line.replace(b'e+6', br'e+06')
            # Compare tag
            if re.match(RE_ELEMENT, expected_line) and not raw:
                expected_elements = re.findall(RE_ELEMENT, expected_line)
                response_elements = re.findall(RE_ELEMENT, response_line)
                self.assertEqual(expected_elements[0],
                                 response_elements[0],
                                 msg=msg +
                                 "\nTag mismatch on line %s: %s != %s" %
                                 (line_no, expected_line, response_line))
                # Compare content
                if len(expected_elements
                       ) == 2 and expected_elements[0] == expected_elements[1]:
                    expected_element_content = re.findall(
                        RE_ELEMENT_CONTENT, expected_line)
                    response_element_content = re.findall(
                        RE_ELEMENT_CONTENT, response_line)
                    self.assertEqual(
                        len(expected_element_content),
                        len(response_element_content),
                        msg=msg + "\nContent mismatch on line %s: %s != %s" %
                        (line_no, expected_line, response_line))
                    if len(expected_element_content):
                        self.assertEqual(
                            expected_element_content[0],
                            response_element_content[0],
                            msg=msg +
                            "\nContent mismatch on line %s: %s != %s" %
                            (line_no, expected_line, response_line))
            else:
                self.assertEqual(expected_line,
                                 response_line,
                                 msg=msg +
                                 "\nTag line mismatch %s: %s != %s\n%s" %
                                 (line_no, expected_line, response_line, msg))
            # print("---->%s\t%s == %s" % (line_no, expected_line, response_line))
            # Compare attributes
            if re.findall(RE_ATTRIBUTES, expected_line):  # has attrs
                expected_attrs, expected_values = zip(
                    *sorted(re.findall(RE_ATTRIBUTES, expected_line)))
                self.assertTrue(
                    re.findall(RE_ATTRIBUTES, response_line),
                    msg=msg +
                    "\nXML attributes differ at line {0}: {1} != {2}".format(
                        line_no, expected_line, response_line))
                response_attrs, response_values = zip(
                    *sorted(re.findall(RE_ATTRIBUTES, response_line)))
                self.assertEqual(
                    expected_attrs,
                    response_attrs,
                    msg=msg +
                    "\nXML attributes differ at line {0}: {1} != {2}".format(
                        line_no, expected_attrs, response_attrs))
                self.assertEqual(
                    expected_values,
                    response_values,
                    msg=msg +
                    "\nXML attribute values differ at line {0}: {1} != {2}".
                    format(line_no, expected_values, response_values))
            line_no += 1

    def setUp(self):
        """Create the server instance"""
        self.fontFamily = QgsFontUtils.standardTestFontFamily()
        QgsFontUtils.loadStandardTestFonts(['All'])

        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")
        self.projectAnnotationPath = os.path.join(
            d, "project_with_annotations.qgs")
        self.projectStatePath = os.path.join(d, "project_state.qgs")
        self.projectUseLayerIdsPath = os.path.join(d,
                                                   "project_use_layerids.qgs")
        self.projectGroupsPath = os.path.join(d, "project_groups.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass

        self.server = QgsServer()

        # Disable landing page API to test standard legacy XML responses in case of errors
        os.environ["QGIS_SERVER_DISABLED_APIS"] = "Landing Page"

    def tearDown(self):
        """Cleanup env"""

        super().tearDown()
        try:
            del os.environ["QGIS_SERVER_DISABLED_APIS"]
        except KeyError:
            pass

    def strip_version_xmlns(self, text):
        """Order of attributes is random, strip version and xmlns"""
        return text.replace(b'version="1.3.0"',
                            b'').replace(b'xmlns="http://www.opengis.net/ogc"',
                                         b'')

    def assert_headers(self, header, body):
        stream = StringIO()
        header_string = header.decode('utf-8')
        stream.write(header_string)
        headers = email.message_from_string(header_string)
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(
                content_length,
                body_length,
                msg=
                "Header reported content-length: %d Actual body length was: %d"
                % (content_length, body_length))

    @classmethod
    def store_reference(self, reference_path, response):
        """Utility to store reference files"""

        # Normally this is false
        if not self.regenerate_reference:
            return

        # Store the output for debug or to regenerate the reference documents:
        f = open(reference_path, 'wb+')
        f.write(response)
        f.close()

    def _result(self, data):
        headers = {}
        for line in data[0].decode('UTF-8').split("\n"):
            if line != "":
                header = line.split(":")
                self.assertEqual(len(header), 2, line)
                headers[str(header[0])] = str(header[1]).strip()

        return data[1], headers

    def _img_diff(self,
                  image,
                  control_image,
                  max_diff,
                  max_size_diff=QSize(),
                  outputFormat='PNG'):

        if outputFormat == 'PNG':
            extFile = 'png'
        elif outputFormat == 'JPG':
            extFile = 'jpg'
        elif outputFormat == 'WEBP':
            extFile = 'webp'
        else:
            raise RuntimeError('Yeah, new format implemented')

        temp_image = os.path.join(tempfile.gettempdir(),
                                  "%s_result.%s" % (control_image, extFile))

        with open(temp_image, "wb") as f:
            f.write(image)

        if outputFormat != 'PNG':
            return (True, "QgsRenderChecker can only be used for PNG")

        control = QgsMultiRenderChecker()
        control.setControlPathPrefix("qgis_server")
        control.setControlName(control_image)
        control.setRenderedImage(temp_image)
        if max_size_diff.isValid():
            control.setSizeTolerance(max_size_diff.width(),
                                     max_size_diff.height())
        return control.runTest(control_image, max_diff), control.report()

    def _img_diff_error(self,
                        response,
                        headers,
                        image,
                        max_diff=100,
                        max_size_diff=QSize(),
                        unittest_data_path='control_images',
                        outputFormat='PNG'):
        """
        :param outputFormat: PNG, JPG or WEBP
        """

        if outputFormat == 'PNG':
            extFile = 'png'
            contentType = 'image/png'
        elif outputFormat == 'JPG':
            extFile = 'jpg'
            contentType = 'image/jpeg'
        elif outputFormat == 'WEBP':
            extFile = 'webp'
            contentType = 'image/webp'
        else:
            raise RuntimeError('Yeah, new format implemented')

        reference_path = unitTestDataPath(
            unittest_data_path
        ) + '/qgis_server/' + image + '/' + image + '.' + extFile
        self.store_reference(reference_path, response)

        self.assertEqual(
            headers.get("Content-Type"), contentType,
            "Content type is wrong: %s instead of %s\n%s" %
            (headers.get("Content-Type"), contentType, response))

        test, report = self._img_diff(response, image, max_diff, max_size_diff,
                                      outputFormat)

        with open(
                os.path.join(tempfile.gettempdir(),
                             image + "_result." + extFile),
                "rb") as rendered_file:
            encoded_rendered_file = base64.b64encode(rendered_file.read())
            if not os.environ.get('ENCODED_OUTPUT'):
                message = "Image is wrong: rendered file %s/%s_result.%s" % (
                    tempfile.gettempdir(), image, extFile)
            else:
                message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.%s" % (
                    report, encoded_rendered_file.strip().decode('utf8'),
                    tempfile.gettempdir(), image, extFile)

        # If the failure is in image sizes the diff file will not exists.
        if os.path.exists(
                os.path.join(tempfile.gettempdir(),
                             image + "_result_diff." + extFile)):
            with open(
                    os.path.join(tempfile.gettempdir(),
                                 image + "_result_diff." + extFile),
                    "rb") as diff_file:
                if not os.environ.get('ENCODED_OUTPUT'):
                    message = "Image is wrong: diff file %s/%s_result_diff.%s" % (
                        tempfile.gettempdir(), image, extFile)
                else:
                    encoded_diff_file = base64.b64encode(diff_file.read())
                    message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.%s" % (
                        encoded_diff_file.strip().decode('utf8'),
                        tempfile.gettempdir(), image, extFile)

        self.assertTrue(test, message)

    def _execute_request(self,
                         qs,
                         requestMethod=QgsServerRequest.GetMethod,
                         data=None,
                         request_headers=None):
        request = QgsBufferServerRequest(qs, requestMethod, request_headers
                                         or {}, data)
        response = QgsBufferServerResponse()
        self.server.handleRequest(request, response)
        headers = []
        rh = response.headers()
        rk = sorted(rh.keys())
        for k in rk:
            headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
        return b"\n".join(headers) + b"\n\n", bytes(response.body())

    def _execute_request_project(self,
                                 qs,
                                 project,
                                 requestMethod=QgsServerRequest.GetMethod,
                                 data=None):
        request = QgsBufferServerRequest(qs, requestMethod, {}, data)
        response = QgsBufferServerResponse()
        self.server.handleRequest(request, response, project)
        headers = []
        rh = response.headers()
        rk = sorted(rh.keys())
        for k in rk:
            headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
        return b"\n".join(headers) + b"\n\n", bytes(response.body())

    def _assert_status_code(self,
                            status_code,
                            qs,
                            requestMethod=QgsServerRequest.GetMethod,
                            data=None,
                            project=None):
        request = QgsBufferServerRequest(qs, requestMethod, {}, data)
        response = QgsBufferServerResponse()
        self.server.handleRequest(request, response, project)
        assert response.statusCode() == status_code, "%s != %s" % (
            response.statusCode(), status_code)

    def _assertRed(self, color: QColor):
        self.assertEqual(color.red(), 255)
        self.assertEqual(color.green(), 0)
        self.assertEqual(color.blue(), 0)

    def _assertGreen(self, color: QColor):
        self.assertEqual(color.red(), 0)
        self.assertEqual(color.green(), 255)
        self.assertEqual(color.blue(), 0)

    def _assertBlue(self, color: QColor):
        self.assertEqual(color.red(), 0)
        self.assertEqual(color.green(), 0)
        self.assertEqual(color.blue(), 255)

    def _assertBlack(self, color: QColor):
        self.assertEqual(color.red(), 0)
        self.assertEqual(color.green(), 0)
        self.assertEqual(color.blue(), 255)

    def _assertWhite(self, color: QColor):
        self.assertEqual(color.red(), 255)
        self.assertEqual(color.green(), 255)
        self.assertEqual(color.blue(), 255)
예제 #23
0
class TestQgsServerPlugins(QgsServerTestBase):
    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):
            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def onSendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.onSendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clear()
                    request.setResponseHeader('Content-type', 'text/plain')
                    request.appendBody(
                        'Hello from SimpleServer!'.encode('utf-8'))

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # global to be modified inside plugin filters
        globals()['status_code'] = 0
        # body to be checked inside plugin filters
        globals()['body2'] = None
        # headers to be checked inside plugin filters
        globals()['headers2'] = None

        # Register some more filters
        class Filter1(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!'.encode('utf-8'))

        class Filter2(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!'.encode('utf-8'))

        class Filter3(QgsServerFilter):
            """Test get and set status code"""
            def responseComplete(self):
                global status_code
                request = self.serverInterface().requestHandler()
                request.setStatusCode(999)
                status_code = request.statusCode()

        class Filter4(QgsServerFilter):
            """Body getter"""
            def responseComplete(self):
                global body2
                request = self.serverInterface().requestHandler()
                body2 = request.body()

        class Filter5(QgsServerFilter):
            """Body setter, clear body, keep headers"""
            def responseComplete(self):
                global headers2
                request = self.serverInterface().requestHandler()
                request.clearBody()
                headers2 = request.responseHeaders()
                request.appendBody('new body, new life!'.encode('utf-8'))

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        filter3 = Filter3(serverIface)
        filter4 = Filter4(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        serverIface.registerFilter(filter3, 300)
        serverIface.registerFilter(filter4, 400)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self._execute_request('?service=simple')]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Check status code
        self.assertEqual(status_code, 999)

        # Check body getter from filter
        self.assertEqual(
            body2,
            b'Hello from SimpleServer!Hello from Filter1!Hello from Filter2!')

        # Check that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = self._execute_request('?service=simple')
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Now, re-run with body setter
        filter5 = Filter5(serverIface)
        serverIface.registerFilter(filter5, 500)
        header, body = self._execute_request('?service=simple')
        response = header + body
        expected = b'Content-Length: 19\nContent-type: text/plain\n\nnew body, new life!'
        self.assertEqual(response, expected)
        self.assertEqual(headers2, {'Content-type': 'text/plain'})

    def test_configpath(self):
        """ Test plugin can read confif path
        """
        try:
            from qgis.server import QgsServerFilter
            from qgis.core import QgsProject
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")
        self.server = QgsServer()

        # global to be modified inside plugin filters
        globals()['configFilePath2'] = None

        class Filter0(QgsServerFilter):
            """Body setter, clear body, keep headers"""
            def requestReady(self):
                global configFilePath2
                configFilePath2 = self.serverInterface().configFilePath()

        serverIface = self.server.serverInterface()
        serverIface.registerFilter(Filter0(serverIface), 100)

        # Test using MAP
        self._execute_request('?service=simple&MAP=%s' % self.projectPath)

        # Check config file path
        self.assertEqual(configFilePath2, self.projectPath)

        # Reset result
        globals()['configFilePath2'] = None

        # Test with prqject as argument
        project = QgsProject()
        project.read(self.projectPath)

        self._execute_request_project('?service=simple', project=project)

        # Check config file path
        self.assertEqual(configFilePath2, project.fileName())

    def test_exceptions(self):
        """Test that plugin filter Python exceptions can be caught"""

        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class FilterBroken(QgsServerFilter):
            def responseComplete(self):
                raise Exception("There was something very wrong!")

        serverIface = self.server.serverInterface()
        filter1 = FilterBroken(serverIface)
        filters = {100: [filter1]}
        serverIface.setFilters(filters)
        header, body = self._execute_request('')
        self.assertEqual(body, b'Internal Server Error')
        serverIface.setFilters({})

    def test_get_path(self):
        """Test get url and path from plugins"""

        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class Filter1(QgsServerFilter):
            def responseComplete(self):
                handler = self.serverInterface().requestHandler()
                self.url = handler.url()
                self.path = handler.path()

        serverIface = self.server.serverInterface()
        filter1 = Filter1(serverIface)
        filters = {100: [filter1]}
        serverIface.setFilters(filters)
        header, body = self._execute_request(
            'http://myserver/mypath/?myparam=1')
        self.assertEqual(filter1.url, 'http://myserver/mypath/?myparam=1')
        self.assertEqual(filter1.path, '/mypath/')

        serverIface.setFilters({})

    def test_streaming_pipeline(self):
        """ Test streaming pipeline propagation
        """
        try:
            from qgis.server import QgsServerFilter
            from qgis.core import QgsProject
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        # create a service for streaming data
        class StreamedService(QgsService):
            def __init__(self):
                super().__init__()
                self._response = b"Should never appear"
                self._name = "TestStreamedService"
                self._version = "1.0"

            def name(self):
                return self._name

            def version(self):
                return self._version

            def executeRequest(self, request, response, project):
                response.setStatusCode(206)
                response.write(self._response)
                response.flush()

        class Filter1(QgsServerFilter):
            def onRequestReady(self):
                request = self.serverInterface().requestHandler()
                return self.propagate

            def onSendResponse(self):
                request = self.serverInterface().requestHandler()
                request.clearBody()
                request.appendBody(b'A')
                request.sendResponse()
                request.appendBody(b'B')
                request.sendResponse()
                # Stop propagating
                return self.propagate

            def onResponseComplete(self):
                request = self.serverInterface().requestHandler()
                request.appendBody(b'C')
                return self.propagate

        # Methods should be called only if filter1 propagate
        class Filter2(QgsServerFilter):
            def __init__(self, iface):
                super().__init__(iface)
                self.request_ready = False

            def onRequestReady(self):
                request = self.serverInterface().requestHandler()
                self.request_ready = True
                return True

            def onSendResponse(self):
                request = self.serverInterface().requestHandler()
                request.appendBody(b'D')
                return True

            def onResponseComplete(self):
                request = self.serverInterface().requestHandler()
                request.appendBody(b'E')
                return True

        serverIface = self.server.serverInterface()
        serverIface.setFilters({})

        service0 = StreamedService()

        reg = serverIface.serviceRegistry()
        reg.registerService(service0)

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 200)
        serverIface.registerFilter(filter2, 300)

        project = QgsProject()

        # Test no propagation
        filter1.propagate = False
        _, body = self._execute_request_project('?service=%s' %
                                                service0.name(),
                                                project=project)
        self.assertFalse(filter2.request_ready)
        self.assertEqual(body, b'ABC')

        # Test with propagation
        filter1.propagate = True
        _, body = self._execute_request_project('?service=%s' %
                                                service0.name(),
                                                project=project)
        self.assertTrue(filter2.request_ready)
        self.assertEqual(body, b'ABDCE')

        serverIface.setFilters({})
        reg.unregisterService(service0.name(), service0.version())
예제 #24
0
class TestQgsServer(unittest.TestCase):

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'
        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def assert_headers(self, header, body):
        headers = Message(StringIO(header))
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(content_length, body_length, msg="Header reported content-length: %d Actual body length was: %d" % (content_length, body_length))

    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server

    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest()

    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        header, body = [str(_v) for _v in self.server.handleRequest()]
        response = header + body
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(response, expected)
        expected = 'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)
        # Test body
        expected = '<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        self.assertEqual(body, expected)

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):

            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clearHeaders()
                    request.setHeader('Content-type', 'text/plain')
                    request.clearBody()
                    request.appendBody('Hello from SimpleServer!')

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # Register some more filters
        class Filter1(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!')

        class Filter2(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!')

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Test that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [str(_v) for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = 'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

    # WMS tests
    def wms_request_compare(self, request, extra=None, reference_file=None):
        project = self.testdata_path + "test+project.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (urllib.quote(project), request)
        if extra is not None:
            query_string += extra
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        response = header + body
        f = open(self.testdata_path + (request.lower() if not reference_file else reference_file) + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        #"""
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace('typeName="Integer64" precision="0" length="10" editType="TextEdit" type="qlonglong"', 'typeName="Integer" precision="0" length="10" editType="TextEdit" type="int"')

        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)

        # Test getfeatureinfo response
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'info_format=text%2Fhtml&transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-html')

        # Test getfeatureinfo default info_format
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-plain')

        # Regression for #8656
        # Mind the gap! (the space in the FILTER expression)
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&' +
                                 'INFO_FORMAT=text%2Fxml&' +
                                 'width=600&height=400&srs=EPSG%3A3857&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&' +
                                 'FEATURE_COUNT=10&FILTER=testlayer%20%C3%A8%C3%A9' + urllib.quote(':"NAME" = \'two\''),
                                 'wms_getfeatureinfo_filter')

    def wms_inspire_request_compare(self, request):
        """WMS INSPIRE tests"""
        project = self.testdata_path + "test+project_inspire.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        response = header + body
        f = open(self.testdata_path + request.lower() + '_inspire.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wms_inspire(self):
        """Test some WMS request"""
        for request in ('GetCapabilities',):
            self.wms_inspire_request_compare(request)

    # WFS tests
    def wfs_request_compare(self, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_' + request.lower() + '.txt')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace('<element type="long" name="id"/>', '<element type="integer" name="id"/>')

        self.assertEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected, response))

    def test_project_wfs(self):
        """Test some WFS request"""
        for request in ('GetCapabilities', 'DescribeFeatureType'):
            self.wfs_request_compare(request)

    def wfs_getfeature_compare(self, requestid, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.quote(project), request)
        header, body = [str(_v) for _v in self.server.handleRequest(query_string)]
        self.result_compare(
            'wfs_getfeature_' + requestid + '.txt',
            u"request %s failed.\n Query: %s" % (
                query_string,
                request,
            ),
            header, body
        )

    def result_compare(self, file_name, error_msg_header, header, body):
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + file_name)
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_PATH, '', response)
        expected = re.sub(RE_STRIP_PATH, '', expected)
        self.assertEqual(response, expected, msg=u"%s\n Expected:\n%s\n\n Response:\n%s"
                                                 % (error_msg_header,
                                                    unicode(expected, errors='replace'),
                                                    unicode(response, errors='replace')))

    def test_getfeature(self):
        tests = []
        tests.append(('nobbox', u'GetFeature&TYPENAME=testlayer'))
        tests.append(('startindex2', u'GetFeature&TYPENAME=testlayer&STARTINDEX=2'))
        tests.append(('limit2', u'GetFeature&TYPENAME=testlayer&MAXFEATURES=2'))
        tests.append(('start1_limit1', u'GetFeature&TYPENAME=testlayer&MAXFEATURES=1&STARTINDEX=1'))

        for id, req in tests:
            self.wfs_getfeature_compare(id, req)

    def wfs_getfeature_post_compare(self, requestid, request):
        project = self.testdata_path + "test+project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP={}'.format(urllib.quote(project))
        self.server.putenv("REQUEST_METHOD", "POST")
        self.server.putenv("REQUEST_BODY", request)
        header, body = self.server.handleRequest(query_string)
        self.server.putenv("REQUEST_METHOD", '')
        self.server.putenv("REQUEST_BODY", '')

        self.result_compare(
            'wfs_getfeature_{}.txt'.format(requestid),
            "GetFeature in POST for '{}' failed.".format(requestid),
            header, body,
        )

    def test_getfeature_post(self):
        template = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
  <wfs:Query typeName="testlayer" xmlns:feature="http://www.qgis.org/gml">
    <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
      <ogc:BBOX>
        <ogc:PropertyName>geometry</ogc:PropertyName>
        <gml:Envelope xmlns:gml="http://www.opengis.net/gml">
          <gml:lowerCorner>8 44</gml:lowerCorner>
          <gml:upperCorner>9 45</gml:upperCorner>
        </gml:Envelope>
      </ogc:BBOX>
    </ogc:Filter>
  </wfs:Query>
</wfs:GetFeature>
"""

        tests = []
        tests.append(('nobbox', template.format("")))
        tests.append(('startindex2', template.format('startIndex="2"')))
        tests.append(('limit2', template.format('maxFeatures="2"')))
        tests.append(('start1_limit1', template.format('startIndex="1" maxFeatures="1"')))

        for id, req in tests:
            self.wfs_getfeature_post_compare(id, req)

    def test_getLegendGraphics(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test%2Bproject.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.0.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer+èé',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.iteritems()])
        h, r = self.server.handleRequest(qs)
        self.assertEqual(-1, h.find('Content-Type: text/xml; charset=utf-8'), "Header: %s\nResponse:\n%s" % (h, r))
        self.assertNotEquals(-1, h.find('Content-Type: image/png'), "Header: %s\nResponse:\n%s" % (h, r))
예제 #25
0
class TestQgsServer(unittest.TestCase):
    def assertXMLEqual(self, response, expected, msg=''):
        """Compare XML line by line and sorted attributes"""
        response_lines = response.splitlines()
        expected_lines = expected.splitlines()
        line_no = 1
        for expected_line in expected_lines:
            expected_line = expected_line.strip()
            response_line = response_lines[line_no - 1].strip()
            # Compare tag
            try:
                self.assertEqual(
                    re.findall(b'<([^>\s]+)[ >]', expected_line)[0],
                    re.findall(b'<([^>\s]+)[ >]', response_line)[0],
                    msg=msg + "\nTag mismatch on line %s: %s != %s" %
                    (line_no, expected_line, response_line))
            except IndexError:
                self.assertEqual(expected_line,
                                 response_line,
                                 msg=msg + "\nTag line mismatch %s: %s != %s" %
                                 (line_no, expected_line, response_line))
            #print("---->%s\t%s == %s" % (line_no, expected_line, response_line))
            # Compare attributes
            if re.match(RE_ATTRIBUTES, expected_line):  # has attrs
                expected_attrs = sorted(
                    re.findall(RE_ATTRIBUTES, expected_line))
                response_attrs = sorted(
                    re.findall(RE_ATTRIBUTES, response_line))
                self.assertEqual(
                    expected_attrs,
                    response_attrs,
                    msg=msg +
                    "\nXML attributes differ at line {0}: {1} != {2}".format(
                        line_no, expected_attrs, response_attrs))
            line_no += 1

    @classmethod
    def setUpClass(cls):
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def strip_version_xmlns(self, text):
        """Order of attributes is random, strip version and xmlns"""
        return text.replace(b'version="1.3.0"',
                            b'').replace(b'xmlns="http://www.opengis.net/ogc"',
                                         b'')

    def assert_headers(self, header, body):
        stream = StringIO()
        header_string = header.decode('utf-8')
        stream.write(header_string)
        headers = email.message_from_string(header_string)
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(
                content_length,
                body_length,
                msg=
                "Header reported content-length: %d Actual body length was: %d"
                % (content_length, body_length))

    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server

    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest()

    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        header, body = [_v for _v in self.server.handleRequest()]
        response = self.strip_version_xmlns(header + body)
        expected = self.strip_version_xmlns(
            b'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        )
        self.assertEqual(response, expected)
        expected = b'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)
        # Test body
        expected = self.strip_version_xmlns(
            b'<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n'
        )
        self.assertEqual(self.strip_version_xmlns(body), expected)

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):
            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clearHeaders()
                    request.setHeader('Content-type', 'text/plain')
                    request.clearBody()
                    request.appendBody(
                        'Hello from SimpleServer!'.encode('utf-8'))

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # Register some more filters
        class Filter1(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!'.encode('utf-8'))

        class Filter2(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!'.encode('utf-8'))

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [
            _v for _v in self.server.handleRequest('service=simple')
        ]
        response = header + body
        expected = b'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Test that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [
            _v for _v in self.server.handleRequest('service=simple')
        ]
        response = header + body
        expected = b'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

    # WMS tests
    def wms_request_compare(self, request, extra=None, reference_file=None):
        project = self.testdata_path + "test_project.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (
            urllib.parse.quote(project), request)
        if extra is not None:
            query_string += extra
        header, body = self.server.handleRequest(query_string)
        response = header + body
        reference_path = self.testdata_path + (
            request.lower() if not reference_file else reference_file) + '.txt'
        f = open(reference_path, 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(reference_path, 'wb+')
        f.write(response)
        f.close()

        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        #"""
        response = re.sub(RE_STRIP_UNCHECKABLE, b'*****', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'*****', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace(
                b'typeName="Integer64" precision="0" length="10" editType="TextEdit" type="qlonglong"',
                b'typeName="Integer" precision="0" length="10" editType="TextEdit" type="int"'
            )

        self.assertXMLEqual(
            response,
            expected,
            msg=
            "request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
            % (query_string, request, expected.decode('utf-8'),
               response.decode('utf-8')))

    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)

        # Test getfeatureinfo response
        self.wms_request_compare(
            'GetFeatureInfo', '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
            'info_format=text%2Fhtml&transparent=true&' +
            'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
            '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
            'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
            'wms_getfeatureinfo-text-html')

        # Test getfeatureinfo default info_format
        self.wms_request_compare(
            'GetFeatureInfo',
            '&layers=testlayer%20%C3%A8%C3%A9&styles=&' + 'transparent=true&' +
            'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
            '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
            'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
            'wms_getfeatureinfo-text-plain')

        # Regression for #8656
        # Mind the gap! (the space in the FILTER expression)
        self.wms_request_compare(
            'GetFeatureInfo',
            '&layers=testlayer%20%C3%A8%C3%A9&' + 'INFO_FORMAT=text%2Fxml&' +
            'width=600&height=400&srs=EPSG%3A3857&' +
            'query_layers=testlayer%20%C3%A8%C3%A9&' +
            'FEATURE_COUNT=10&FILTER=testlayer%20%C3%A8%C3%A9' +
            urllib.parse.quote(':"NAME" = \'two\''),
            'wms_getfeatureinfo_filter')

    def wms_inspire_request_compare(self, request):
        """WMS INSPIRE tests"""
        project = self.testdata_path + "test_project_inspire.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (
            urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        response = header + body
        f = open(self.testdata_path + request.lower() + '_inspire.txt', 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)
        self.assertXMLEqual(
            response,
            expected,
            msg=
            "request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
            % (query_string, request, expected.decode('utf-8'),
               response.decode('utf-8')))

    def test_project_wms_inspire(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', ):
            self.wms_inspire_request_compare(request)

    # WFS tests
    def wfs_request_compare(self, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (
            urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_' + request.lower() + '.txt', 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace(
                b'<element type="long" name="id"/>',
                b'<element type="integer" name="id"/>')

        self.assertXMLEqual(
            response,
            expected,
            msg=
            "request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s"
            % (query_string, request, expected.decode('utf-8'),
               response.decode('utf-8')))

    def test_project_wfs(self):
        """Test some WFS request"""
        for request in ('GetCapabilities', 'DescribeFeatureType'):
            self.wfs_request_compare(request)

    def wfs_getfeature_compare(self, requestid, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (
            urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        self.result_compare(
            'wfs_getfeature_' + requestid + '.txt',
            "request %s failed.\n Query: %s" % (
                query_string,
                request,
            ), header, body)

    def result_compare(self, file_name, error_msg_header, header, body):
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + file_name, 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)
        self.assertXMLEqual(response,
                            expected,
                            msg="%s\n Expected:\n%s\n\n Response:\n%s" %
                            (error_msg_header, str(expected, errors='replace'),
                             str(response, errors='replace')))

    def test_getfeature(self):
        tests = []
        tests.append(('nobbox', 'GetFeature&TYPENAME=testlayer'))
        tests.append(
            ('startindex2', 'GetFeature&TYPENAME=testlayer&STARTINDEX=2'))
        tests.append(('limit2', 'GetFeature&TYPENAME=testlayer&MAXFEATURES=2'))
        tests.append(
            ('start1_limit1',
             'GetFeature&TYPENAME=testlayer&MAXFEATURES=1&STARTINDEX=1'))

        for id, req in tests:
            self.wfs_getfeature_compare(id, req)

    def wfs_getfeature_post_compare(self, requestid, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP={}'.format(urllib.parse.quote(project))
        self.server.putenv("REQUEST_METHOD", "POST")
        self.server.putenv("REQUEST_BODY", request)
        header, body = self.server.handleRequest(query_string)
        self.server.putenv("REQUEST_METHOD", '')
        self.server.putenv("REQUEST_BODY", '')

        self.result_compare(
            'wfs_getfeature_{}.txt'.format(requestid),
            "GetFeature in POST for '{}' failed.".format(requestid),
            header,
            body,
        )

    def test_getfeature_post(self):
        template = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
  <wfs:Query typeName="testlayer" xmlns:feature="http://www.qgis.org/gml">
    <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
      <ogc:BBOX>
        <ogc:PropertyName>geometry</ogc:PropertyName>
        <gml:Envelope xmlns:gml="http://www.opengis.net/gml">
          <gml:lowerCorner>8 44</gml:lowerCorner>
          <gml:upperCorner>9 45</gml:upperCorner>
        </gml:Envelope>
      </ogc:BBOX>
    </ogc:Filter>
  </wfs:Query>
</wfs:GetFeature>
"""

        tests = []
        tests.append(('nobbox', template.format("")))
        tests.append(('startindex2', template.format('startIndex="2"')))
        tests.append(('limit2', template.format('maxFeatures="2"')))
        tests.append(('start1_limit1',
                      template.format('startIndex="1" maxFeatures="1"')))

        for id, req in tests:
            self.wfs_getfeature_post_compare(id, req)

    def test_wms_getmap_order(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetMap",
                "LAYERS": "Hello,Country",
                "STYLES": "",
                "FORMAT": "image/png",
                "BBOX": "-16817707,-4710778,5696513,14587125",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_LayerOrder")

    def test_wms_getmap_srs(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetMap",
                "LAYERS": "Country,Hello",
                "STYLES": "",
                "FORMAT": "image/png",
                "BBOX": "-151.7,-38.9,51.0,78.0",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:4326"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_SRS")

    def test_wms_getmap_style(self):
        # default style
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetMap",
                "LAYERS": "Country_Labels",
                "STYLES": "",
                "FORMAT": "image/png",
                "BBOX": "-16817707,-4710778,5696513,14587125",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_StyleDefault")

        # custom style
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetMap",
                "LAYERS": "Country_Labels",
                "STYLES": "custom",
                "FORMAT": "image/png",
                "BBOX": "-16817707,-4710778,5696513,14587125",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_StyleCustom")

    def test_wms_getmap_filter(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetMap",
                "LAYERS": "Country,Hello",
                "STYLES": "",
                "FORMAT": "image/png",
                "BBOX": "-16817707,-4710778,5696513,14587125",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857",
                "FILTER": "Country:\"name\" = 'eurasia'"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Filter")

    def test_wms_getmap_selection(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetMap",
                "LAYERS": "Country,Hello",
                "STYLES": "",
                "FORMAT": "image/png",
                "BBOX": "-16817707,-4710778,5696513,14587125",
                "HEIGHT": "500",
                "WIDTH": "500",
                "SRS": "EPSG:3857",
                "SELECTION": "Country_Labels: 4"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Selection")

    def test_wms_getmap_opacities(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetMap",
                "LAYERS": "Country,Hello",
                "STYLES": "",
                "FORMAT": "image/png",
                "BBOX": "-16817707,-4710778,5696513,14587125",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857",
                "OPACITIES": "125, 50"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetMap_Opacities")

    def test_wms_getprint_basic(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetPrint",
                "TEMPLATE": "layoutA4",
                "FORMAT": "png",
                "map0:EXTENT":
                "-33626185.498,-13032965.185,33978427.737,16020257.031",
                "map0:LAYERS": "Country,Hello",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Basic")

    def test_wms_getprint_srs(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetPrint",
                "TEMPLATE": "layoutA4",
                "FORMAT": "png",
                "map0:EXTENT": "-309.015,-133.011,312.179,133.949",
                "map0:LAYERS": "Country,Hello",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:4326"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_SRS")

    def test_wms_getprint_scale(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetPrint",
                "TEMPLATE": "layoutA4",
                "FORMAT": "png",
                "map0:EXTENT":
                "-33626185.498,-13032965.185,33978427.737,16020257.031",
                "map0:LAYERS": "Country,Hello",
                "map0:SCALE": "36293562",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Scale")

    def test_wms_getprint_grid(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetPrint",
                "TEMPLATE": "layoutA4",
                "FORMAT": "png",
                "map0:EXTENT":
                "-33626185.498,-13032965.185,33978427.737,16020257.031",
                "map0:LAYERS": "Country,Hello",
                "map0:GRID_INTERVAL_X": "1000000",
                "map0:GRID_INTERVAL_Y": "2000000",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Grid")

    def test_wms_getprint_rotation(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetPrint",
                "TEMPLATE": "layoutA4",
                "FORMAT": "png",
                "map0:EXTENT":
                "-33626185.498,-13032965.185,33978427.737,16020257.031",
                "map0:LAYERS": "Country,Hello",
                "map0:ROTATION": "45",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetPrint_Rotation")

    def test_getLegendGraphics(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': 'testlayer%20èé',
        }
        qs = '&'.join(["%s=%s" % (k, v) for k, v in parms.items()])
        h, r = self.server.handleRequest(qs)
        self.assertEqual(-1, h.find(b'Content-Type: text/xml; charset=utf-8'),
                         "Header: %s\nResponse:\n%s" % (h, r))
        self.assertNotEqual(-1, h.find(b'Content-Type: image/png'),
                            "Header: %s\nResponse:\n%s" % (h, r))

    def test_getLegendGraphics_layertitle(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer%20èé',
            'LAYERTITLE': 'TRUE',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.items()])
        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_test", 250,
                             QSize(10, 10))

        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer%20èé',
            'LAYERTITLE': 'FALSE',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.items()])
        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h,
                             "WMS_GetLegendGraphic_test_layertitle_false", 250,
                             QSize(10, 10))

    def test_wms_GetLegendGraphic_Basic(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetLegendGraphic",
                "LAYER": "Country,Hello",
                "LAYERTITLE": "FALSE",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_Basic")

    def test_wms_GetLegendGraphic_BoxSpace(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetLegendGraphic",
                "LAYER": "Country,Hello",
                "LAYERTITLE": "FALSE",
                "BOXSPACE": "100",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_BoxSpace")

    def test_wms_GetLegendGraphic_SymbolSpace(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetLegendGraphic",
                "LAYER": "Country,Hello",
                "LAYERTITLE": "FALSE",
                "SYMBOLSPACE": "100",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_SymbolSpace")

    def test_wms_GetLegendGraphic_IconLabelSpace(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetLegendGraphic",
                "LAYER": "Country,Hello",
                "LAYERTITLE": "FALSE",
                "ICONLABELSPACE": "100",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_IconLabelSpace")

    def test_wms_GetLegendGraphic_SymbolSize(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetLegendGraphic",
                "LAYER": "Country,Hello",
                "LAYERTITLE": "FALSE",
                "SYMBOLWIDTH": "50",
                "SYMBOLHEIGHT": "30",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "CRS": "EPSG:3857"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_SymbolSize")

    def test_wms_GetLegendGraphic_BBox(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetLegendGraphic",
                "LAYER": "Country,Hello,db_point",
                "LAYERTITLE": "FALSE",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "BBOX": "-151.7,-38.9,51.0,78.0",
                "CRS": "EPSG:4326"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_BBox")

    def test_wms_GetLegendGraphic_BBox2(self):
        qs = "&".join([
            "%s=%s" % i
            for i in list({
                "MAP": urllib.parse.quote(self.projectPath),
                "SERVICE": "WMS",
                "VERSION": "1.1.1",
                "REQUEST": "GetLegendGraphic",
                "LAYER": "Country,Hello,db_point",
                "LAYERTITLE": "FALSE",
                "FORMAT": "image/png",
                "HEIGHT": "500",
                "WIDTH": "500",
                "BBOX": "-76.08,-6.4,-19.38,38.04",
                "SRS": "EPSG:4326"
            }.items())
        ])

        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_BBox2")

    def _result(self, data):
        headers = {}
        for line in data[0].decode('UTF-8').split("\n"):
            if line != "":
                header = line.split(":")
                self.assertEqual(len(header), 2, line)
                headers[str(header[0])] = str(header[1]).strip()

        return data[1], headers

    def _img_diff(self, image, control_image, max_diff, max_size_diff=QSize()):
        temp_image = os.path.join(tempfile.gettempdir(),
                                  "%s_result.png" % control_image)

        with open(temp_image, "wb") as f:
            f.write(image)

        control = QgsRenderChecker()
        control.setControlPathPrefix("qgis_server")
        control.setControlName(control_image)
        control.setRenderedImage(temp_image)
        if max_size_diff.isValid():
            control.setSizeTolerance(max_size_diff.width(),
                                     max_size_diff.height())
        return control.compareImages(control_image), control.report()

    def _img_diff_error(self,
                        response,
                        headers,
                        image,
                        max_diff=10,
                        max_size_diff=QSize()):
        self.assertEqual(
            headers.get("Content-Type"), "image/png",
            "Content type is wrong: %s" % headers.get("Content-Type"))
        test, report = self._img_diff(response, image, max_diff, max_size_diff)

        with open(os.path.join(tempfile.gettempdir(), image + "_result.png"),
                  "rb") as rendered_file:
            encoded_rendered_file = base64.b64encode(rendered_file.read())
            message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.png" % (
                report, encoded_rendered_file.strip(), tempfile.gettempdir(),
                image)

        # If the failure is in image sizes the diff file will not exists.
        if os.path.exists(
                os.path.join(tempfile.gettempdir(),
                             image + "_result_diff.png")):
            with open(
                    os.path.join(tempfile.gettempdir(),
                                 image + "_result_diff.png"),
                    "rb") as diff_file:
                encoded_diff_file = base64.b64encode(diff_file.read())
                message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.png" % (
                    encoded_diff_file.strip(), tempfile.gettempdir(), image)

        self.assertTrue(test, message)
예제 #26
0
QGIS_SERVER_PKI_CERTIFICATE = os.environ.get('QGIS_SERVER_PKI_CERTIFICATE')
QGIS_SERVER_PKI_KEY = os.environ.get('QGIS_SERVER_PKI_KEY')
QGIS_SERVER_PKI_AUTHORITY = os.environ.get('QGIS_SERVER_PKI_AUTHORITY')
QGIS_SERVER_PKI_USERNAME = os.environ.get('QGIS_SERVER_PKI_USERNAME')

# Check if PKI - https is enabled
https = (QGIS_SERVER_PKI_CERTIFICATE is not None
         and os.path.isfile(QGIS_SERVER_PKI_CERTIFICATE)
         and QGIS_SERVER_PKI_KEY is not None
         and os.path.isfile(QGIS_SERVER_PKI_KEY)
         and QGIS_SERVER_PKI_AUTHORITY is not None
         and os.path.isfile(QGIS_SERVER_PKI_AUTHORITY)
         and QGIS_SERVER_PKI_USERNAME)

qgs_app = QgsApplication([], False)
qgs_server = QgsServer()

if os.environ.get('QGIS_SERVER_HTTP_BASIC_AUTH') is not None:
    from qgis.server import QgsServerFilter
    import base64

    class HTTPBasicFilter(QgsServerFilter):
        def responseComplete(self):
            request = self.serverInterface().requestHandler()
            if self.serverInterface().getEnv('HTTP_AUTHORIZATION'):
                username, password = base64.b64decode(
                    self.serverInterface().getEnv(
                        'HTTP_AUTHORIZATION')[6:]).split(b':')
                if (username.decode('utf-8') == os.environ.get(
                        'QGIS_SERVER_USERNAME', 'username')
                        and password.decode('utf-8') == os.environ.get(
예제 #27
0
from qgis.core import QgsMessageLog
from qgis.server import QgsServer, QgsServerFilter

standard_library.install_aliases()

__author__ = 'Alessandro Pasotti'
__date__ = '05/15/2016'
__copyright__ = 'Copyright 2016, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'

QGIS_SERVER_PORT = int(os.environ.get('QGIS_SERVER_PORT', '8083'))
#QGIS_SERVER_HOST = os.environ.get('QGIS_SERVER_HOST', '127.0.0.1')
QGIS_SERVER_HOST = ''  # listen an all interfaces

qgs_server = QgsServer()

QGIS_SERVER_OAUTH2_USERNAME = os.environ.get('QGIS_SERVER_OAUTH2_USERNAME',
                                             'username')
QGIS_SERVER_OAUTH2_PASSWORD = os.environ.get('QGIS_SERVER_OAUTH2_PASSWORD',
                                             'password')
QGIS_SERVER_OAUTH2_TOKEN_EXPIRES_IN = os.environ.get(
    'QGIS_SERVER_OAUTH2_TOKEN_EXPIRES_IN', 3600)

print("SERVER_NAME (from env, will be used fo urls): %s" %
      os.environ.get('SERVER_NAME'))
print("OAuth2 username: %s" % QGIS_SERVER_OAUTH2_USERNAME)
print("OAuth2 password: %s" % QGIS_SERVER_OAUTH2_PASSWORD)

# Naive token storage implementation
_tokens = {}
import urllib.parse
from http.server import BaseHTTPRequestHandler, HTTPServer
from qgis.server import QgsServer


try:
    QGIS_SERVER_DEFAULT_PORT = int(os.environ['QGIS_SERVER_DEFAULT_PORT'])
except KeyError:
    QGIS_SERVER_DEFAULT_PORT = 8081
try:
    QGIS_SERVER_DEFAULT_SERVERNAME = os.environ['QGIS_SERVER_DEFAULT_SERVERNAME']
except KeyError:
    QGIS_SERVER_DEFAULT_SERVERNAME = 'localhost'


qgs_server = QgsServer()

# OAuth 2 plugin loading start
serverIface = qgs_server.serverInterface()
from oauth_settings import *
import importlib
module = importlib.import_module('filters.%s' % OAUTH2_AUTHORIZATION_SERVICE_PROVIDER)
klass_name = 'OAuth2Filter%s' % OAUTH2_AUTHORIZATION_SERVICE_PROVIDER.title()
klass = getattr(module, klass_name)
serverIface.registerFilter(klass(serverIface), 100)
# OAuth 2 plugin loading End


class Handler(BaseHTTPRequestHandler):

    def __init__(self, request, client_address, server):
예제 #29
0
            attributes.remove("colour")
        return attributes

    def allowToEdit(self, layer, feature):
        """ Are we authorise to modify the following geometry """

        if not self._active:
            return super(RestrictedAccessControl, self).allowToEdit(layer, feature)

        return feature.attribute("color") in ["red", "yellow"]

    def cacheKey(self):
        return "r" if self._active else "f"


server = QgsServer()
server.handleRequest()
server_iface = server.serverInterface()
accesscontrol = RestrictedAccessControl(server_iface)
server_iface.registerAccessControl(accesscontrol, 100)


class TestQgsServerAccessControl(unittest.TestCase):

    def setUp(self):
        self.testdata_path = unitTestDataPath("qgis_server_accesscontrol")

        dataFile = os.path.join(self.testdata_path, "helloworld.db")
        self.assertTrue(os.path.isfile(dataFile), 'Could not find data file "{}"'.format(dataFile))
        copyfile(dataFile, os.path.join(self.testdata_path, "_helloworld.db"))
예제 #30
0
# Check if OAuth2 is enabled
QGIS_SERVER_OAUTH2_AUTH = (
    QGIS_SERVER_OAUTH2_CERTIFICATE is not None and
    os.path.isfile(QGIS_SERVER_OAUTH2_CERTIFICATE) and
    QGIS_SERVER_OAUTH2_KEY is not None and
    os.path.isfile(QGIS_SERVER_OAUTH2_KEY) and
    QGIS_SERVER_OAUTH2_AUTHORITY is not None and
    os.path.isfile(QGIS_SERVER_OAUTH2_AUTHORITY) and
    QGIS_SERVER_OAUTH2_USERNAME and QGIS_SERVER_OAUTH2_PASSWORD)

HTTPS_ENABLED = QGIS_SERVER_PKI_AUTH or QGIS_SERVER_OAUTH2_AUTH


qgs_app = QgsApplication([], False)
qgs_server = QgsServer()


if QGIS_SERVER_HTTP_BASIC_AUTH:
    from qgis.server import QgsServerFilter
    import base64

    class HTTPBasicFilter(QgsServerFilter):

        def requestReady(self):
            handler = self.serverInterface().requestHandler()
            auth = self.serverInterface().requestHandler().requestHeader('HTTP_AUTHORIZATION')
            if auth:
                username, password = base64.b64decode(auth[6:]).split(b':')
                if (username.decode('utf-8') == os.environ.get('QGIS_SERVER_USERNAME', 'username') and
                        password.decode('utf-8') == os.environ.get('QGIS_SERVER_PASSWORD', 'password')):
예제 #31
0
 def test_destructor_segfaults(self):
     """Segfault on destructor?"""
     server = QgsServer()
     del server
예제 #32
0
class TestQgsServer(unittest.TestCase):

    def assertXMLEqual(self, response, expected, msg=''):
        """Compare XML line by line and sorted attributes"""
        response_lines = response.splitlines()
        expected_lines = expected.splitlines()
        line_no = 1
        for expected_line in expected_lines:
            expected_line = expected_line.strip()
            response_line = response_lines[line_no - 1].strip()
            # Compare tag
            try:
                self.assertEqual(re.findall(b'<([^>\s]+)[ >]', expected_line)[0],
                                 re.findall(b'<([^>\s]+)[ >]', response_line)[0], msg=msg + "\nTag mismatch on line %s: %s != %s" % (line_no, expected_line, response_line))
            except IndexError:
                self.assertEqual(expected_line, response_line, msg=msg + "\nTag line mismatch %s: %s != %s" % (line_no, expected_line, response_line))
            #print("---->%s\t%s == %s" % (line_no, expected_line, response_line))
            # Compare attributes
            if re.match(RE_ATTRIBUTES, expected_line): # has attrs
                expected_attrs = re.findall(RE_ATTRIBUTES, expected_line)
                expected_attrs.sort()
                response_attrs = re.findall(RE_ATTRIBUTES, response_line)
                response_attrs.sort()
                self.assertEqual(expected_attrs, response_attrs, msg=msg + "\nXML attributes differ at line {0}: {1} != {2}".format(line_no, expected_attrs, response_attrs))
            line_no += 1

    @classmethod
    def setUpClass(cls):
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()

    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'
        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def strip_version_xmlns(self, text):
        """Order of attributes is random, strip version and xmlns"""
        return text.replace(b'version="1.3.0"', b'').replace(b'xmlns="http://www.opengis.net/ogc"', b'')

    def assert_headers(self, header, body):
        stream = StringIO()
        header_string = header.decode('utf-8')
        stream.write(header_string)
        headers = email.message_from_string(header_string)
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(content_length, body_length, msg="Header reported content-length: %d Actual body length was: %d" % (content_length, body_length))

    def test_destructor_segfaults(self):
        """Segfault on destructor?"""
        server = QgsServer()
        del server

    def test_multiple_servers(self):
        """Segfaults?"""
        for i in range(10):
            locals()["s%s" % i] = QgsServer()
            locals()["s%s" % i].handleRequest()

    def test_api(self):
        """Using an empty query string (returns an XML exception)
        we are going to test if headers and body are returned correctly"""
        # Test as a whole
        header, body = [_v for _v in self.server.handleRequest()]
        response = self.strip_version_xmlns(header + body)
        expected = self.strip_version_xmlns(b'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n')
        self.assertEqual(response, expected)
        expected = b'Content-Length: 206\nContent-Type: text/xml; charset=utf-8\n\n'
        self.assertEqual(header, expected)
        # Test body
        expected = self.strip_version_xmlns(b'<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc">\n <ServiceException code="Service configuration error">Service unknown or unsupported</ServiceException>\n</ServiceExceptionReport>\n')
        self.assertEqual(self.strip_version_xmlns(body), expected)

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):

            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clearHeaders()
                    request.setHeader('Content-type', 'text/plain')
                    request.clearBody()
                    request.appendBody('Hello from SimpleServer!'.encode('utf-8'))

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # Register some more filters
        class Filter1(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!'.encode('utf-8'))

        class Filter2(QgsServerFilter):

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!'.encode('utf-8'))

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = b'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Test that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self.server.handleRequest('service=simple')]
        response = header + body
        expected = b'Content-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

    # WMS tests
    def wms_request_compare(self, request, extra=None, reference_file=None):
        project = self.testdata_path + "test_project.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3&REQUEST=%s' % (urllib.parse.quote(project), request)
        if extra is not None:
            query_string += extra
        header, body = self.server.handleRequest(query_string)
        response = header + body
        reference_path = self.testdata_path + (request.lower() if not reference_file else reference_file) + '.txt'
        f = open(reference_path, 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(reference_path, 'wb+')
        f.write(response)
        f.close()

        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        #"""
        response = re.sub(RE_STRIP_UNCHECKABLE, b'*****', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'*****', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace(b'typeName="Integer64" precision="0" length="10" editType="TextEdit" type="qlonglong"', b'typeName="Integer" precision="0" length="10" editType="TextEdit" type="int"')

        self.assertXMLEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected.decode('utf-8'), response.decode('utf-8')))

    def test_project_wms(self):
        """Test some WMS request"""
        for request in ('GetCapabilities', 'GetProjectSettings'):
            self.wms_request_compare(request)

        # Test getfeatureinfo response
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'info_format=text%2Fhtml&transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-html')

        # Test getfeatureinfo default info_format
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&styles=&' +
                                 'transparent=true&' +
                                 'width=600&height=400&srs=EPSG%3A3857&bbox=913190.6389747962%2C' +
                                 '5606005.488876367%2C913235.426296057%2C5606035.347090538&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&X=190&Y=320',
                                 'wms_getfeatureinfo-text-plain')

        # Regression for #8656
        # Mind the gap! (the space in the FILTER expression)
        self.wms_request_compare('GetFeatureInfo',
                                 '&layers=testlayer%20%C3%A8%C3%A9&' +
                                 'INFO_FORMAT=text%2Fxml&' +
                                 'width=600&height=400&srs=EPSG%3A3857&' +
                                 'query_layers=testlayer%20%C3%A8%C3%A9&' +
                                 'FEATURE_COUNT=10&FILTER=testlayer%20%C3%A8%C3%A9' + urllib.parse.quote(':"NAME" = \'two\''),
                                 'wms_getfeatureinfo_filter')

    def wms_inspire_request_compare(self, request):
        """WMS INSPIRE tests"""
        project = self.testdata_path + "test_project_inspire.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WMS&VERSION=1.3.0&REQUEST=%s' % (urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        response = header + body
        f = open(self.testdata_path + request.lower() + '_inspire.txt', 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)
        self.assertXMLEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected.decode('utf-8'), response.decode('utf-8')))

    def test_project_wms_inspire(self):
        """Test some WMS request"""
        for request in ('GetCapabilities',):
            self.wms_inspire_request_compare(request)

    # WFS tests
    def wfs_request_compare(self, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + 'wfs_' + request.lower() + '.txt', 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_' +  request.lower() + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)

        # for older GDAL versions (<2.0), id field will be integer type
        if int(osgeo.gdal.VersionInfo()[:1]) < 2:
            expected = expected.replace(b'<element type="long" name="id"/>', b'<element type="integer" name="id"/>')

        self.assertXMLEqual(response, expected, msg="request %s failed.\n Query: %s\n Expected:\n%s\n\n Response:\n%s" % (query_string, request, expected.decode('utf-8'), response.decode('utf-8')))

    def test_project_wfs(self):
        """Test some WFS request"""
        for request in ('GetCapabilities', 'DescribeFeatureType'):
            self.wfs_request_compare(request)

    def wfs_getfeature_compare(self, requestid, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP=%s&SERVICE=WFS&VERSION=1.0.0&REQUEST=%s' % (urllib.parse.quote(project), request)
        header, body = self.server.handleRequest(query_string)
        self.result_compare(
            'wfs_getfeature_' + requestid + '.txt',
            "request %s failed.\n Query: %s" % (
                query_string,
                request,
            ),
            header, body
        )

    def result_compare(self, file_name, error_msg_header, header, body):
        self.assert_headers(header, body)
        response = header + body
        f = open(self.testdata_path + file_name, 'rb')
        expected = f.read()
        f.close()
        # Store the output for debug or to regenerate the reference documents:
        """
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_expected.txt', 'w+')
        f.write(expected)
        f.close()
        f = open(os.path.dirname(__file__) + '/wfs_getfeature_' +  requestid + '_response.txt', 'w+')
        f.write(response)
        f.close()
        """
        response = re.sub(RE_STRIP_UNCHECKABLE, b'', response)
        expected = re.sub(RE_STRIP_UNCHECKABLE, b'', expected)
        self.assertXMLEqual(response, expected, msg="%s\n Expected:\n%s\n\n Response:\n%s"
                            % (error_msg_header,
                                                    str(expected, errors='replace'),
                                                    str(response, errors='replace')))

    def test_getfeature(self):
        tests = []
        tests.append(('nobbox', 'GetFeature&TYPENAME=testlayer'))
        tests.append(('startindex2', 'GetFeature&TYPENAME=testlayer&STARTINDEX=2'))
        tests.append(('limit2', 'GetFeature&TYPENAME=testlayer&MAXFEATURES=2'))
        tests.append(('start1_limit1', 'GetFeature&TYPENAME=testlayer&MAXFEATURES=1&STARTINDEX=1'))

        for id, req in tests:
            self.wfs_getfeature_compare(id, req)

    def wfs_getfeature_post_compare(self, requestid, request):
        project = self.testdata_path + "test_project_wfs.qgs"
        assert os.path.exists(project), "Project file not found: " + project

        query_string = 'MAP={}'.format(urllib.parse.quote(project))
        self.server.putenv("REQUEST_METHOD", "POST")
        self.server.putenv("REQUEST_BODY", request)
        header, body = self.server.handleRequest(query_string)
        self.server.putenv("REQUEST_METHOD", '')
        self.server.putenv("REQUEST_BODY", '')

        self.result_compare(
            'wfs_getfeature_{}.txt'.format(requestid),
            "GetFeature in POST for '{}' failed.".format(requestid),
            header, body,
        )

    def test_getfeature_post(self):
        template = """<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="1.0.0" {} xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
  <wfs:Query typeName="testlayer" xmlns:feature="http://www.qgis.org/gml">
    <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
      <ogc:BBOX>
        <ogc:PropertyName>geometry</ogc:PropertyName>
        <gml:Envelope xmlns:gml="http://www.opengis.net/gml">
          <gml:lowerCorner>8 44</gml:lowerCorner>
          <gml:upperCorner>9 45</gml:upperCorner>
        </gml:Envelope>
      </ogc:BBOX>
    </ogc:Filter>
  </wfs:Query>
</wfs:GetFeature>
"""

        tests = []
        tests.append(('nobbox', template.format("")))
        tests.append(('startindex2', template.format('startIndex="2"')))
        tests.append(('limit2', template.format('maxFeatures="2"')))
        tests.append(('start1_limit1', template.format('startIndex="1" maxFeatures="1"')))

        for id, req in tests:
            self.wfs_getfeature_post_compare(id, req)

    def test_getLegendGraphics(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': 'testlayer%20èé',
        }
        qs = '&'.join(["%s=%s" % (k, v) for k, v in parms.items()])
        print(qs)
        h, r = self.server.handleRequest(qs)
        self.assertEqual(-1, h.find(b'Content-Type: text/xml; charset=utf-8'), "Header: %s\nResponse:\n%s" % (h, r))
        self.assertNotEqual(-1, h.find(b'Content-Type: image/png'), "Header: %s\nResponse:\n%s" % (h, r))

    def test_getLegendGraphics_layertitle(self):
        """Test that does not return an exception but an image"""
        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer%20èé',
            'LAYERTITLE': 'TRUE',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.items()])
        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_test", 250, QSize(10, 10))

        parms = {
            'MAP': self.testdata_path + "test_project.qgs",
            'SERVICE': 'WMS',
            'VERSION': '1.3.0',
            'REQUEST': 'GetLegendGraphic',
            'FORMAT': 'image/png',
            #'WIDTH': '20', # optional
            #'HEIGHT': '20', # optional
            'LAYER': u'testlayer%20èé',
            'LAYERTITLE': 'FALSE',
        }
        qs = '&'.join([u"%s=%s" % (k, v) for k, v in parms.items()])
        r, h = self._result(self.server.handleRequest(qs))
        self._img_diff_error(r, h, "WMS_GetLegendGraphic_test_layertitle_false", 250, QSize(10, 10))

    def _result(self, data):
        headers = {}
        for line in data[0].decode('UTF-8').split("\n"):
            if line != "":
                header = line.split(":")
                self.assertEqual(len(header), 2, line)
                headers[str(header[0])] = str(header[1]).strip()

        return data[1], headers

    def _img_diff(self, image, control_image, max_diff, max_size_diff=QSize()):
        temp_image = os.path.join(tempfile.gettempdir(), "%s_result.png" % control_image)

        with open(temp_image, "wb") as f:
            f.write(image)

        control = QgsRenderChecker()
        control.setControlPathPrefix("qgis_server")
        control.setControlName(control_image)
        control.setRenderedImage(temp_image)
        if max_size_diff.isValid():
            control.setSizeTolerance(max_size_diff.width(), max_size_diff.height())
        return control.compareImages(control_image), control.report()

    def _img_diff_error(self, response, headers, image, max_diff=10, max_size_diff=QSize()):
        self.assertEqual(
            headers.get("Content-Type"), "image/png",
            "Content type is wrong: %s" % headers.get("Content-Type"))
        test, report = self._img_diff(response, image, max_diff, max_size_diff)

        with open(os.path.join(tempfile.gettempdir(), image + "_result.png"), "rb") as rendered_file:
            encoded_rendered_file = base64.b64encode(rendered_file.read())
            message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.png" % (
                report, encoded_rendered_file.strip(), tempfile.gettempdir(), image
            )

        with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file:
            encoded_diff_file = base64.b64encode(diff_file.read())
            message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.png" % (
                encoded_diff_file.strip(), tempfile.gettempdir(), image
            )

        self.assertTrue(test, message)
예제 #33
0
class QgsServerTestBase(unittest.TestCase):
    """Base class for QGIS server tests"""

    # Set to True in child classes to re-generate reference files for this class
    regenerate_reference = False

    def assertXMLEqual(self, response, expected, msg=''):
        """Compare XML line by line and sorted attributes"""
        response_lines = response.splitlines()
        expected_lines = expected.splitlines()
        line_no = 1
        self.assertEqual(
            len(expected_lines), len(response_lines),
            "Expected and response have different number of lines!\n{}".format(
                msg))
        for expected_line in expected_lines:
            expected_line = expected_line.strip()
            response_line = response_lines[line_no - 1].strip()
            response_line = response_line.replace(b'e+6', b'e+06')
            # Compare tag
            if re.match(RE_ELEMENT, expected_line):
                expected_elements = re.findall(RE_ELEMENT, expected_line)
                response_elements = re.findall(RE_ELEMENT, response_line)
                self.assertEqual(expected_elements[0],
                                 response_elements[0],
                                 msg=msg +
                                 "\nTag mismatch on line %s: %s != %s" %
                                 (line_no, expected_line, response_line))
                # Compare content
                if len(expected_elements
                       ) == 2 and expected_elements[0] == expected_elements[1]:
                    expected_element_content = re.findall(
                        RE_ELEMENT_CONTENT, expected_line)
                    response_element_content = re.findall(
                        RE_ELEMENT_CONTENT, response_line)
                    self.assertEqual(
                        len(expected_element_content),
                        len(response_element_content),
                        msg=msg + "\nContent mismatch on line %s: %s != %s" %
                        (line_no, expected_line, response_line))
                    if len(expected_element_content):
                        self.assertEqual(
                            expected_element_content[0],
                            response_element_content[0],
                            msg=msg +
                            "\nContent mismatch on line %s: %s != %s" %
                            (line_no, expected_line, response_line))
            else:
                self.assertEqual(expected_line,
                                 response_line,
                                 msg=msg +
                                 "\nTag line mismatch %s: %s != %s\n%s" %
                                 (line_no, expected_line, response_line, msg))
            # print("---->%s\t%s == %s" % (line_no, expected_line, response_line))
            # Compare attributes
            if re.findall(RE_ATTRIBUTES, expected_line):  # has attrs
                expected_attrs, expected_values = zip(
                    *sorted(re.findall(RE_ATTRIBUTES, expected_line)))
                self.assertTrue(
                    re.findall(RE_ATTRIBUTES, response_line),
                    msg=msg +
                    "\nXML attributes differ at line {0}: {1} != {2}".format(
                        line_no, expected_line, response_line))
                response_attrs, response_values = zip(
                    *sorted(re.findall(RE_ATTRIBUTES, response_line)))
                self.assertEqual(
                    expected_attrs,
                    response_attrs,
                    msg=msg +
                    "\nXML attributes differ at line {0}: {1} != {2}".format(
                        line_no, expected_attrs, response_attrs))
                self.assertEqual(
                    expected_values,
                    response_values,
                    msg=msg +
                    "\nXML attribute values differ at line {0}: {1} != {2}".
                    format(line_no, expected_values, response_values))
            line_no += 1

    @classmethod
    def setUpClass(cls):
        cls.app = QgsApplication([], False)

    @classmethod
    def tearDownClass(cls):
        cls.app.exitQgis()

    def setUp(self):
        """Create the server instance"""
        self.fontFamily = QgsFontUtils.standardTestFontFamily()
        QgsFontUtils.loadStandardTestFonts(['All'])

        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")
        self.projectAnnotationPath = os.path.join(
            d, "project_with_annotations.qgs")
        self.projectStatePath = os.path.join(d, "project_state.qgs")
        self.projectUseLayerIdsPath = os.path.join(d,
                                                   "project_use_layerids.qgs")
        self.projectGroupsPath = os.path.join(d, "project_groups.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def strip_version_xmlns(self, text):
        """Order of attributes is random, strip version and xmlns"""
        return text.replace(b'version="1.3.0"',
                            b'').replace(b'xmlns="http://www.opengis.net/ogc"',
                                         b'')

    def assert_headers(self, header, body):
        stream = StringIO()
        header_string = header.decode('utf-8')
        stream.write(header_string)
        headers = email.message_from_string(header_string)
        if 'content-length' in headers:
            content_length = int(headers['content-length'])
            body_length = len(body)
            self.assertEqual(
                content_length,
                body_length,
                msg=
                "Header reported content-length: %d Actual body length was: %d"
                % (content_length, body_length))

    @classmethod
    def store_reference(self, reference_path, response):
        """Utility to store reference files"""

        # Normally this is false
        if not self.regenerate_reference:
            return

        # Store the output for debug or to regenerate the reference documents:
        f = open(reference_path, 'wb+')
        f.write(response)
        f.close()

    def _result(self, data):
        headers = {}
        for line in data[0].decode('UTF-8').split("\n"):
            if line != "":
                header = line.split(":")
                self.assertEqual(len(header), 2, line)
                headers[str(header[0])] = str(header[1]).strip()

        return data[1], headers

    def _img_diff(self, image, control_image, max_diff, max_size_diff=QSize()):
        temp_image = os.path.join(tempfile.gettempdir(),
                                  "%s_result.png" % control_image)

        with open(temp_image, "wb") as f:
            f.write(image)

        control = QgsRenderChecker()
        control.setControlPathPrefix("qgis_server")
        control.setControlName(control_image)
        control.setRenderedImage(temp_image)
        if max_size_diff.isValid():
            control.setSizeTolerance(max_size_diff.width(),
                                     max_size_diff.height())
        return control.compareImages(control_image, max_diff), control.report()

    def _img_diff_error(self,
                        response,
                        headers,
                        image,
                        max_diff=100,
                        max_size_diff=QSize()):
        self.assertEqual(
            headers.get("Content-Type"), "image/png",
            "Content type is wrong: %s\n%s" %
            (headers.get("Content-Type"), response))

        test, report = self._img_diff(response, image, max_diff, max_size_diff)

        with open(os.path.join(tempfile.gettempdir(), image + "_result.png"),
                  "rb") as rendered_file:
            encoded_rendered_file = base64.b64encode(rendered_file.read())
            if not os.environ.get('ENCODED_OUTPUT'):
                message = "Image is wrong\: rendered file %s/%s_result.png" % (
                    tempfile.gettempdir(), image)
            else:
                message = "Image is wrong\n%s\nImage:\necho '%s' | base64 -d >%s/%s_result.png" % (
                    report, encoded_rendered_file.strip().decode('utf8'),
                    tempfile.gettempdir(), image)

        # If the failure is in image sizes the diff file will not exists.
        if os.path.exists(
                os.path.join(tempfile.gettempdir(),
                             image + "_result_diff.png")):
            with open(
                    os.path.join(tempfile.gettempdir(),
                                 image + "_result_diff.png"),
                    "rb") as diff_file:
                if not os.environ.get('ENCODED_OUTPUT'):
                    message = "Image is wrong\: diff file %s/%s_result_diff.png" % (
                        tempfile.gettempdir(), image)
                else:
                    encoded_diff_file = base64.b64encode(diff_file.read())
                    message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.png" % (
                        encoded_diff_file.strip().decode('utf8'),
                        tempfile.gettempdir(), image)

        self.assertTrue(test, message)

    def _execute_request(self,
                         qs,
                         requestMethod=QgsServerRequest.GetMethod,
                         data=None):
        request = QgsBufferServerRequest(qs, requestMethod, {}, data)
        response = QgsBufferServerResponse()
        self.server.handleRequest(request, response)
        headers = []
        rh = response.headers()
        rk = sorted(rh.keys())
        for k in rk:
            headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
        return b"\n".join(headers) + b"\n\n", bytes(response.body())

    def _execute_request_project(self,
                                 qs,
                                 project,
                                 requestMethod=QgsServerRequest.GetMethod,
                                 data=None):
        request = QgsBufferServerRequest(qs, requestMethod, {}, data)
        response = QgsBufferServerResponse()
        self.server.handleRequest(request, response, project)
        headers = []
        rh = response.headers()
        rk = sorted(rh.keys())
        for k in rk:
            headers.append(("%s: %s" % (k, rh[k])).encode('utf-8'))
        return b"\n".join(headers) + b"\n\n", bytes(response.body())
예제 #34
0
 def setUp(self):
     self.server = QgsServer()
     copyfile(self.db, self.db_clone)
예제 #35
0
class TestQgsServerPlugins(QgsServerTestBase):
    def setUp(self):
        """Create the server instance"""
        self.testdata_path = unitTestDataPath('qgis_server') + '/'

        d = unitTestDataPath('qgis_server_accesscontrol') + '/'
        self.projectPath = os.path.join(d, "project.qgs")

        # Clean env just to be sure
        env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
        for ev in env_vars:
            try:
                del os.environ[ev]
            except KeyError:
                pass
        self.server = QgsServer()

    def test_pluginfilters(self):
        """Test python plugins filters"""
        try:
            from qgis.server import QgsServerFilter
        except ImportError:
            print("QGIS Server plugins are not compiled. Skipping test")
            return

        class SimpleHelloFilter(QgsServerFilter):
            def requestReady(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.requestReady")

            def sendResponse(self):
                QgsMessageLog.logMessage("SimpleHelloFilter.sendResponse")

            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                QgsMessageLog.logMessage("SimpleHelloFilter.responseComplete")
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.clear()
                    request.setResponseHeader('Content-type', 'text/plain')
                    request.appendBody(
                        'Hello from SimpleServer!'.encode('utf-8'))

        serverIface = self.server.serverInterface()
        filter = SimpleHelloFilter(serverIface)
        serverIface.registerFilter(filter, 100)
        # Get registered filters
        self.assertEqual(filter, serverIface.filters()[100][0])

        # global to be modified inside plugin filters
        globals()['status_code'] = 0
        # body to be checked inside plugin filters
        globals()['body2'] = None
        # headers to be checked inside plugin filters
        globals()['headers2'] = None

        # Register some more filters
        class Filter1(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter1!'.encode('utf-8'))

        class Filter2(QgsServerFilter):
            def responseComplete(self):
                request = self.serverInterface().requestHandler()
                params = request.parameterMap()
                if params.get('SERVICE', '').upper() == 'SIMPLE':
                    request.appendBody('Hello from Filter2!'.encode('utf-8'))

        class Filter3(QgsServerFilter):
            """Test get and set status code"""
            def responseComplete(self):
                global status_code
                request = self.serverInterface().requestHandler()
                request.setStatusCode(999)
                status_code = request.statusCode()

        class Filter4(QgsServerFilter):
            """Body getter"""
            def responseComplete(self):
                global body2
                request = self.serverInterface().requestHandler()
                body2 = request.body()

        class Filter5(QgsServerFilter):
            """Body setter, clear body, keep headers"""
            def responseComplete(self):
                global headers2
                request = self.serverInterface().requestHandler()
                request.clearBody()
                headers2 = request.responseHeaders()
                request.appendBody('new body, new life!'.encode('utf-8'))

        filter1 = Filter1(serverIface)
        filter2 = Filter2(serverIface)
        filter3 = Filter3(serverIface)
        filter4 = Filter4(serverIface)
        serverIface.registerFilter(filter1, 101)
        serverIface.registerFilter(filter2, 200)
        serverIface.registerFilter(filter2, 100)
        serverIface.registerFilter(filter3, 300)
        serverIface.registerFilter(filter4, 400)
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self._execute_request('?service=simple')]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Check status code
        self.assertEqual(status_code, 999)

        # Check body getter from filter
        self.assertEqual(
            body2,
            b'Hello from SimpleServer!Hello from Filter1!Hello from Filter2!')

        # Check that the bindings for complex type QgsServerFiltersMap are working
        filters = {100: [filter, filter2], 101: [filter1], 200: [filter2]}
        serverIface.setFilters(filters)
        self.assertTrue(filter in serverIface.filters()[100])
        self.assertTrue(filter2 in serverIface.filters()[100])
        self.assertEqual(filter1, serverIface.filters()[101][0])
        self.assertEqual(filter2, serverIface.filters()[200][0])
        header, body = [_v for _v in self._execute_request('?service=simple')]
        response = header + body
        expected = b'Content-Length: 62\nContent-type: text/plain\n\nHello from SimpleServer!Hello from Filter1!Hello from Filter2!'
        self.assertEqual(response, expected)

        # Now, re-run with body setter
        filter5 = Filter5(serverIface)
        serverIface.registerFilter(filter5, 500)
        header, body = [_v for _v in self._execute_request('?service=simple')]
        response = header + body
        expected = b'Content-Length: 19\nContent-type: text/plain\n\nnew body, new life!'
        self.assertEqual(response, expected)
        self.assertEqual(headers2, {'Content-type': 'text/plain'})
예제 #36
0
QGIS_SERVER_PKI_KEY = os.environ.get('QGIS_SERVER_PKI_KEY')
QGIS_SERVER_PKI_AUTHORITY = os.environ.get('QGIS_SERVER_PKI_AUTHORITY')
QGIS_SERVER_PKI_USERNAME = os.environ.get('QGIS_SERVER_PKI_USERNAME')

# Check if PKI - https is enabled
https = (QGIS_SERVER_PKI_CERTIFICATE is not None and
         os.path.isfile(QGIS_SERVER_PKI_CERTIFICATE) and
         QGIS_SERVER_PKI_KEY is not None and
         os.path.isfile(QGIS_SERVER_PKI_KEY) and
         QGIS_SERVER_PKI_AUTHORITY is not None and
         os.path.isfile(QGIS_SERVER_PKI_AUTHORITY) and
         QGIS_SERVER_PKI_USERNAME)


qgs_app = QgsApplication([], False)
qgs_server = QgsServer()


if os.environ.get('QGIS_SERVER_HTTP_BASIC_AUTH') is not None:
    from qgis.server import QgsServerFilter
    import base64

    class HTTPBasicFilter(QgsServerFilter):

        def responseComplete(self):
            request = self.serverInterface().requestHandler()
            if self.serverInterface().getEnv('HTTP_AUTHORIZATION'):
                username, password = base64.b64decode(self.serverInterface().getEnv('HTTP_AUTHORIZATION')[6:]).split(b':')
                if (username.decode('utf-8') == os.environ.get('QGIS_SERVER_USERNAME', 'username')
                        and password.decode('utf-8') == os.environ.get('QGIS_SERVER_PASSWORD', 'password')):
                    return
예제 #37
0
QGIS_SERVER_PKI_CERTIFICATE = os.environ.get('QGIS_SERVER_PKI_CERTIFICATE')
QGIS_SERVER_PKI_KEY = os.environ.get('QGIS_SERVER_PKI_KEY')
QGIS_SERVER_PKI_AUTHORITY = os.environ.get('QGIS_SERVER_PKI_AUTHORITY')
QGIS_SERVER_PKI_USERNAME = os.environ.get('QGIS_SERVER_PKI_USERNAME')

# Check if PKI - https is enabled
https = (QGIS_SERVER_PKI_CERTIFICATE is not None
         and os.path.isfile(QGIS_SERVER_PKI_CERTIFICATE)
         and QGIS_SERVER_PKI_KEY is not None
         and os.path.isfile(QGIS_SERVER_PKI_KEY)
         and QGIS_SERVER_PKI_AUTHORITY is not None
         and os.path.isfile(QGIS_SERVER_PKI_AUTHORITY)
         and QGIS_SERVER_PKI_USERNAME)

qgs_app = QgsApplication([], False)
qgs_server = QgsServer()

# Load plugins
if os.environ.get('QGIS_PLUGINPATH', False):
    plugindir = os.environ.get('QGIS_PLUGINPATH')
    sys.path.append(plugindir)
    # Magic mistery: without this, virtual QgsServerFilter instances are not wrapped
    iface = qgs_server.serverInterface()
    try:
        for modulename in [(a, b, c) for a, b, c in os.walk(plugindir)][0][1]:
            try:
                module = importlib.import_module(modulename)
                getattr(module, 'serverClassFactory')(iface)
                QgsMessageLog.logMessage('Python plugin %s loaded!' %
                                         modulename)
            except: