LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/wfs - ogrwfsdatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1051 1271 82.7 %
Date: 2026-02-12 23:49:34 Functions: 37 37 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  WFS Translator
       4             :  * Purpose:  Implements OGRWFSDataSource class
       5             :  * Author:   Even Rouault, even dot rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "ogr_wfs.h"
      15             : #include "ogr_api.h"
      16             : #include "cpl_minixml.h"
      17             : #include "cpl_http.h"
      18             : #include "gmlutils.h"
      19             : #include "parsexsd.h"
      20             : #include "ogr_swq.h"
      21             : #include "ogr_p.h"
      22             : #include "ogrwfsfilter.h"
      23             : #include "memdataset.h"
      24             : 
      25             : #include <algorithm>
      26             : 
      27             : typedef struct
      28             : {
      29             :     const char *pszPath;
      30             :     const char *pszMDI;
      31             : } MetadataItem;
      32             : 
      33             : static const MetadataItem asMetadata[] = {
      34             :     {"Service.Title", "TITLE"},                        /*1.0 */
      35             :     {"ServiceIdentification.Title", "TITLE"},          /* 1.1 or 2.0 */
      36             :     {"Service.Abstract", "ABSTRACT"},                  /* 1.0 */
      37             :     {"ServiceIdentification.Abstract", "ABSTRACT"},    /* 1.1 or 2.0 */
      38             :     {"ServiceProvider.ProviderName", "PROVIDER_NAME"}, /* 1.1 or 2.0 */
      39             : };
      40             : 
      41             : /************************************************************************/
      42             : /*                            WFSFindNode()                             */
      43             : /************************************************************************/
      44             : 
      45         160 : const CPLXMLNode *WFSFindNode(const CPLXMLNode *psXML, const char *pszRootName)
      46             : {
      47         160 :     const CPLXMLNode *psIter = psXML;
      48          11 :     do
      49             :     {
      50         171 :         if (psIter->eType == CXT_Element)
      51             :         {
      52         163 :             const char *pszNodeName = psIter->pszValue;
      53         163 :             const char *pszSep = strchr(pszNodeName, ':');
      54         163 :             if (pszSep)
      55         123 :                 pszNodeName = pszSep + 1;
      56         163 :             if (EQUAL(pszNodeName, pszRootName))
      57             :             {
      58         145 :                 return psIter;
      59             :             }
      60             :         }
      61          26 :         psIter = psIter->psNext;
      62          26 :     } while (psIter);
      63             : 
      64          15 :     psIter = psXML->psChild;
      65          28 :     while (psIter)
      66             :     {
      67          21 :         if (psIter->eType == CXT_Element)
      68             :         {
      69          19 :             const char *pszNodeName = psIter->pszValue;
      70          19 :             const char *pszSep = strchr(pszNodeName, ':');
      71          19 :             if (pszSep)
      72           1 :                 pszNodeName = pszSep + 1;
      73          19 :             if (EQUAL(pszNodeName, pszRootName))
      74             :             {
      75           8 :                 return psIter;
      76             :             }
      77             :         }
      78          13 :         psIter = psIter->psNext;
      79             :     }
      80           7 :     return nullptr;
      81             : }
      82             : 
      83             : /************************************************************************/
      84             : /*                       OGRWFSWrappedResultLayer                       */
      85             : /************************************************************************/
      86             : 
      87             : class OGRWFSWrappedResultLayer final : public OGRLayer
      88             : {
      89             :     std::unique_ptr<GDALDataset> poDS{};
      90             :     OGRLayer *poLayer = nullptr;
      91             : 
      92             :     CPL_DISALLOW_COPY_ASSIGN(OGRWFSWrappedResultLayer)
      93             : 
      94             :   public:
      95           4 :     OGRWFSWrappedResultLayer(GDALDataset *poDSIn, OGRLayer *poLayerIn)
      96           4 :         : poDS(poDSIn), poLayer(poLayerIn)
      97             :     {
      98           4 :     }
      99             : 
     100             :     void ResetReading() override;
     101             : 
     102           4 :     OGRFeature *GetNextFeature() override
     103             :     {
     104           4 :         return poLayer->GetNextFeature();
     105             :     }
     106             : 
     107           2 :     OGRErr SetNextByIndex(GIntBig nIndex) override
     108             :     {
     109           2 :         return poLayer->SetNextByIndex(nIndex);
     110             :     }
     111             : 
     112           2 :     OGRFeature *GetFeature(GIntBig nFID) override
     113             :     {
     114           2 :         return poLayer->GetFeature(nFID);
     115             :     }
     116             : 
     117           2 :     const OGRFeatureDefn *GetLayerDefn() const override
     118             :     {
     119           2 :         return poLayer->GetLayerDefn();
     120             :     }
     121             : 
     122           2 :     GIntBig GetFeatureCount(int bForce = TRUE) override
     123             :     {
     124           2 :         return poLayer->GetFeatureCount(bForce);
     125             :     }
     126             : 
     127           2 :     int TestCapability(const char *pszCap) const override
     128             :     {
     129           2 :         return poLayer->TestCapability(pszCap);
     130             :     }
     131             : };
     132             : 
     133           2 : void OGRWFSWrappedResultLayer::ResetReading()
     134             : {
     135           2 :     poLayer->ResetReading();
     136           2 : }
     137             : 
     138             : /************************************************************************/
     139             : /*                          OGRWFSDataSource()                          */
     140             : /************************************************************************/
     141             : 
     142         146 : OGRWFSDataSource::OGRWFSDataSource()
     143             :     : bPagingAllowed(
     144         146 :           CPLTestBool(CPLGetConfigOption("OGR_WFS_PAGING_ALLOWED", "OFF"))),
     145         146 :       bLoadMultipleLayerDefn(CPLTestBool(
     146         438 :           CPLGetConfigOption("OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN", "TRUE")))
     147             : {
     148         146 :     if (bPagingAllowed)
     149             :     {
     150             :         const char *pszOption =
     151           2 :             CPLGetConfigOption("OGR_WFS_PAGE_SIZE", nullptr);
     152           2 :         if (pszOption != nullptr)
     153             :         {
     154           2 :             nPageSize = atoi(pszOption);
     155           2 :             if (nPageSize <= 0)
     156           0 :                 nPageSize = DEFAULT_PAGE_SIZE;
     157             :         }
     158             : 
     159           2 :         pszOption = CPLGetConfigOption("OGR_WFS_BASE_START_INDEX", nullptr);
     160           2 :         if (pszOption != nullptr)
     161           0 :             nBaseStartIndex = atoi(pszOption);
     162             :     }
     163         146 : }
     164             : 
     165             : /************************************************************************/
     166             : /*                         ~OGRWFSDataSource()                          */
     167             : /************************************************************************/
     168             : 
     169         292 : OGRWFSDataSource::~OGRWFSDataSource()
     170             : 
     171             : {
     172         146 :     if (psFileXML)
     173             :     {
     174           6 :         if (bRewriteFile)
     175             :         {
     176           1 :             CPLSerializeXMLTreeToFile(psFileXML, GetDescription());
     177             :         }
     178             : 
     179           6 :         CPLDestroyXMLNode(psFileXML);
     180             :     }
     181             : 
     182         359 :     for (int i = 0; i < nLayers; i++)
     183         213 :         delete papoLayers[i];
     184         146 :     CPLFree(papoLayers);
     185             : 
     186         146 :     if (!osLayerMetadataTmpFileName.empty())
     187           1 :         VSIUnlink(osLayerMetadataTmpFileName);
     188         146 :     delete poLayerMetadataDS;
     189         146 :     delete poLayerGetCapabilitiesDS;
     190             : 
     191         146 :     CSLDestroy(papszIdGenMethods);
     192         146 :     CSLDestroy(papszHttpOptions);
     193         292 : }
     194             : 
     195             : /************************************************************************/
     196             : /*                              GetLayer()                              */
     197             : /************************************************************************/
     198             : 
     199         125 : const OGRLayer *OGRWFSDataSource::GetLayer(int iLayer) const
     200             : 
     201             : {
     202         125 :     if (iLayer < 0 || iLayer >= nLayers)
     203           0 :         return nullptr;
     204             :     else
     205         125 :         return papoLayers[iLayer];
     206             : }
     207             : 
     208             : /************************************************************************/
     209             : /*                           GetLayerByName()                           */
     210             : /************************************************************************/
     211             : 
     212         479 : OGRLayer *OGRWFSDataSource::GetLayerByName(const char *pszNameIn)
     213             : {
     214         479 :     if (!pszNameIn)
     215           0 :         return nullptr;
     216             : 
     217         479 :     if (EQUAL(pszNameIn, "WFSLayerMetadata"))
     218             :     {
     219           1 :         if (!osLayerMetadataTmpFileName.empty())
     220           0 :             return poLayerMetadataLayer;
     221             : 
     222             :         osLayerMetadataTmpFileName =
     223           1 :             VSIMemGenerateHiddenFilename("WFSLayerMetadata.csv");
     224           1 :         osLayerMetadataCSV = "layer_name,title,abstract\n" + osLayerMetadataCSV;
     225             : 
     226           1 :         VSIFCloseL(VSIFileFromMemBuffer(
     227             :             osLayerMetadataTmpFileName,
     228           1 :             reinterpret_cast<GByte *>(osLayerMetadataCSV.data()),
     229           1 :             osLayerMetadataCSV.size(), FALSE));
     230           1 :         poLayerMetadataDS =
     231           1 :             GDALDataset::Open(osLayerMetadataTmpFileName, GDAL_OF_VECTOR,
     232             :                               nullptr, nullptr, nullptr);
     233           1 :         if (poLayerMetadataDS)
     234           1 :             poLayerMetadataLayer = poLayerMetadataDS->GetLayer(0);
     235           1 :         return poLayerMetadataLayer;
     236             :     }
     237         478 :     else if (EQUAL(pszNameIn, "WFSGetCapabilities"))
     238             :     {
     239           1 :         if (poLayerGetCapabilitiesLayer != nullptr)
     240           0 :             return poLayerGetCapabilitiesLayer;
     241             : 
     242           1 :         poLayerGetCapabilitiesDS = MEMDataset::Create(
     243             :             "WFSGetCapabilities", 0, 0, 0, GDT_Unknown, nullptr);
     244           1 :         poLayerGetCapabilitiesLayer = poLayerGetCapabilitiesDS->CreateLayer(
     245             :             "WFSGetCapabilities", nullptr, wkbNone, nullptr);
     246           1 :         OGRFieldDefn oFDefn("content", OFTString);
     247           1 :         CPL_IGNORE_RET_VAL(poLayerGetCapabilitiesLayer->CreateField(&oFDefn));
     248             :         OGRFeature *poFeature =
     249           1 :             new OGRFeature(poLayerGetCapabilitiesLayer->GetLayerDefn());
     250           1 :         poFeature->SetField(0, osGetCapabilities);
     251           1 :         CPL_IGNORE_RET_VAL(
     252           1 :             poLayerGetCapabilitiesLayer->CreateFeature(poFeature));
     253           1 :         delete poFeature;
     254             : 
     255           1 :         return poLayerGetCapabilitiesLayer;
     256             :     }
     257             : 
     258         477 :     int nIndex = GetLayerIndex(pszNameIn);
     259         477 :     if (nIndex < 0)
     260           6 :         return nullptr;
     261             :     else
     262         471 :         return papoLayers[nIndex];
     263             : }
     264             : 
     265             : /************************************************************************/
     266             : /*                       GetMetadataDomainList()                        */
     267             : /************************************************************************/
     268             : 
     269           1 : char **OGRWFSDataSource::GetMetadataDomainList()
     270             : {
     271           1 :     return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
     272           1 :                                    "", "xml:capabilities", nullptr);
     273             : }
     274             : 
     275             : /************************************************************************/
     276             : /*                            GetMetadata()                             */
     277             : /************************************************************************/
     278             : 
     279           3 : CSLConstList OGRWFSDataSource::GetMetadata(const char *pszDomain)
     280             : {
     281           3 :     if (pszDomain != nullptr && EQUAL(pszDomain, "xml:capabilities"))
     282             :     {
     283           2 :         apszGetCapabilities[0] = osGetCapabilities.c_str();
     284           2 :         apszGetCapabilities[1] = nullptr;
     285           2 :         return const_cast<char **>(apszGetCapabilities);
     286             :     }
     287           1 :     return GDALDataset::GetMetadata(pszDomain);
     288             : }
     289             : 
     290             : /************************************************************************/
     291             : /*                           GetLayerIndex()                            */
     292             : /************************************************************************/
     293             : 
     294         485 : int OGRWFSDataSource::GetLayerIndex(const char *pszNameIn)
     295             : {
     296         485 :     bool bHasFoundLayerWithColon = false;
     297             : 
     298             :     /* first a case sensitive check */
     299         774 :     for (int i = 0; i < nLayers; i++)
     300             :     {
     301         720 :         OGRWFSLayer *poLayer = papoLayers[i];
     302             : 
     303         720 :         if (strcmp(pszNameIn, poLayer->GetName()) == 0)
     304         431 :             return i;
     305             : 
     306         289 :         bHasFoundLayerWithColon |= strchr(poLayer->GetName(), ':') != nullptr;
     307             :     }
     308             : 
     309             :     /* then case insensitive */
     310         158 :     for (int i = 0; i < nLayers; i++)
     311             :     {
     312         104 :         OGRWFSLayer *poLayer = papoLayers[i];
     313             : 
     314         104 :         if (EQUAL(pszNameIn, poLayer->GetName()))
     315           0 :             return i;
     316             :     }
     317             : 
     318             :     /* now try looking after the colon character */
     319          54 :     if (!bKeepLayerNamePrefix && bHasFoundLayerWithColon &&
     320          48 :         strchr(pszNameIn, ':') == nullptr)
     321             :     {
     322          72 :         for (int i = 0; i < nLayers; i++)
     323             :         {
     324          72 :             OGRWFSLayer *poLayer = papoLayers[i];
     325             : 
     326          72 :             const char *pszAfterColon = strchr(poLayer->GetName(), ':');
     327          72 :             if (pszAfterColon && EQUAL(pszNameIn, pszAfterColon + 1))
     328          48 :                 return i;
     329             :         }
     330             :     }
     331             : 
     332           6 :     return -1;
     333             : }
     334             : 
     335             : /************************************************************************/
     336             : /*                      FindSubStringInsensitive()                      */
     337             : /************************************************************************/
     338             : 
     339         469 : const char *FindSubStringInsensitive(const char *pszStr, const char *pszSubStr)
     340             : {
     341         469 :     size_t nSubStrPos = CPLString(pszStr).ifind(pszSubStr);
     342         469 :     if (nSubStrPos == std::string::npos)
     343         465 :         return nullptr;
     344           4 :     return pszStr + nSubStrPos;
     345             : }
     346             : 
     347             : /************************************************************************/
     348             : /*                   DetectIfGetFeatureSupportHits()                    */
     349             : /************************************************************************/
     350             : 
     351          95 : static bool DetectIfGetFeatureSupportHits(const CPLXMLNode *psRoot)
     352             : {
     353             :     const CPLXMLNode *psOperationsMetadata =
     354          95 :         CPLGetXMLNode(psRoot, "OperationsMetadata");
     355          95 :     if (!psOperationsMetadata)
     356             :     {
     357          61 :         CPLDebug("WFS", "Could not find <OperationsMetadata>");
     358          61 :         return false;
     359             :     }
     360             : 
     361          34 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     362          61 :     while (psChild)
     363             :     {
     364         132 :         if (psChild->eType == CXT_Element &&
     365          88 :             strcmp(psChild->pszValue, "Operation") == 0 &&
     366          44 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0)
     367             :         {
     368          17 :             break;
     369             :         }
     370          27 :         psChild = psChild->psNext;
     371             :     }
     372          34 :     if (!psChild)
     373             :     {
     374          17 :         CPLDebug("WFS", "Could not find <Operation name=\"GetFeature\">");
     375          17 :         return false;
     376             :     }
     377             : 
     378          17 :     psChild = psChild->psChild;
     379          41 :     while (psChild)
     380             :     {
     381         106 :         if (psChild->eType == CXT_Element &&
     382          58 :             strcmp(psChild->pszValue, "Parameter") == 0 &&
     383          17 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "resultType") == 0)
     384             :         {
     385          17 :             break;
     386             :         }
     387          24 :         psChild = psChild->psNext;
     388             :     }
     389          17 :     if (!psChild)
     390             :     {
     391           0 :         CPLDebug("WFS", "Could not find <Parameter name=\"resultType\">");
     392           0 :         return false;
     393             :     }
     394             : 
     395          17 :     psChild = psChild->psChild;
     396          51 :     while (psChild)
     397             :     {
     398          49 :         if (psChild->eType == CXT_Element &&
     399          32 :             strcmp(psChild->pszValue, "Value") == 0)
     400             :         {
     401          32 :             CPLXMLNode *psChild2 = psChild->psChild;
     402          49 :             while (psChild2)
     403             :             {
     404          32 :                 if (psChild2->eType == CXT_Text &&
     405          32 :                     strcmp(psChild2->pszValue, "hits") == 0)
     406             :                 {
     407          15 :                     CPLDebug("WFS", "GetFeature operation supports hits");
     408          15 :                     return true;
     409             :                 }
     410          17 :                 psChild2 = psChild2->psNext;
     411             :             }
     412             :         }
     413          34 :         psChild = psChild->psNext;
     414             :     }
     415             : 
     416           2 :     return false;
     417             : }
     418             : 
     419             : /************************************************************************/
     420             : /*                DetectRequiresEnvelopeSpatialFilter()                 */
     421             : /************************************************************************/
     422             : 
     423         136 : bool OGRWFSDataSource::DetectRequiresEnvelopeSpatialFilter(
     424             :     const CPLXMLNode *psRoot)
     425             : {
     426             :     // This is a heuristic to detect Deegree 3 servers, such as
     427             :     // http://deegree3-demo.deegree.org:80/deegree-utah-demo/services that are
     428             :     // very GML3 strict, and don't like <gml:Box> in a <Filter><BBOX> request,
     429             :     // but requires instead <gml:Envelope>, but some servers (such as MapServer)
     430             :     // don't like <gml:Envelope> so we are obliged to detect the kind of server.
     431             : 
     432         136 :     const CPLXMLNode *psGeometryOperands = CPLGetXMLNode(
     433             :         psRoot, "Filter_Capabilities.Spatial_Capabilities.GeometryOperands");
     434         136 :     if (!psGeometryOperands)
     435             :     {
     436          95 :         return false;
     437             :     }
     438             : 
     439          41 :     int nCount = 0;
     440          41 :     const CPLXMLNode *psChild = psGeometryOperands->psChild;
     441         205 :     while (psChild)
     442             :     {
     443         164 :         nCount++;
     444         164 :         psChild = psChild->psNext;
     445             :     }
     446             :     // Magic number... Might be fragile.
     447          41 :     return nCount == 19;
     448             : }
     449             : 
     450             : /************************************************************************/
     451             : /*                       GetPostTransactionURL()                        */
     452             : /************************************************************************/
     453             : 
     454          62 : CPLString OGRWFSDataSource::GetPostTransactionURL()
     455             : {
     456          62 :     if (!osPostTransactionURL.empty())
     457          62 :         return osPostTransactionURL;
     458             : 
     459           0 :     osPostTransactionURL = osBaseURL;
     460           0 :     const char *pszPostTransactionURL = osPostTransactionURL.c_str();
     461           0 :     const char *pszEsperluet = strchr(pszPostTransactionURL, '?');
     462           0 :     if (pszEsperluet)
     463           0 :         osPostTransactionURL.resize(pszEsperluet - pszPostTransactionURL);
     464             : 
     465           0 :     return osPostTransactionURL;
     466             : }
     467             : 
     468             : /************************************************************************/
     469             : /*                      DetectTransactionSupport()                      */
     470             : /************************************************************************/
     471             : 
     472         137 : bool OGRWFSDataSource::DetectTransactionSupport(const CPLXMLNode *psRoot)
     473             : {
     474             :     const CPLXMLNode *psTransactionWFS100 =
     475         137 :         CPLGetXMLNode(psRoot, "Capability.Request.Transaction");
     476         137 :     if (psTransactionWFS100)
     477             :     {
     478             :         const CPLXMLNode *psPostURL =
     479           0 :             CPLGetXMLNode(psTransactionWFS100, "DCPType.HTTP.Post");
     480           0 :         if (psPostURL)
     481             :         {
     482             :             const char *pszPOSTURL =
     483           0 :                 CPLGetXMLValue(psPostURL, "onlineResource", nullptr);
     484           0 :             if (pszPOSTURL)
     485             :             {
     486           0 :                 osPostTransactionURL = pszPOSTURL;
     487             :             }
     488             :         }
     489             : 
     490           0 :         bTransactionSupport = true;
     491           0 :         return true;
     492             :     }
     493             : 
     494             :     const CPLXMLNode *psOperationsMetadata =
     495         137 :         CPLGetXMLNode(psRoot, "OperationsMetadata");
     496         137 :     if (!psOperationsMetadata)
     497             :     {
     498          69 :         return false;
     499             :     }
     500             : 
     501          68 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     502         189 :     while (psChild)
     503             :     {
     504         420 :         if (psChild->eType == CXT_Element &&
     505         224 :             strcmp(psChild->pszValue, "Operation") == 0 &&
     506          84 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "Transaction") == 0)
     507             :         {
     508          19 :             break;
     509             :         }
     510         121 :         psChild = psChild->psNext;
     511             :     }
     512          68 :     if (!psChild)
     513             :     {
     514          49 :         CPLDebug("WFS", "No transaction support");
     515          49 :         return false;
     516             :     }
     517             : 
     518          19 :     bTransactionSupport = true;
     519          19 :     CPLDebug("WFS", "Transaction support !");
     520             : 
     521          19 :     const CPLXMLNode *psPostURL = CPLGetXMLNode(psChild, "DCP.HTTP.Post");
     522          19 :     if (psPostURL)
     523             :     {
     524          17 :         const char *pszPOSTURL = CPLGetXMLValue(psPostURL, "href", nullptr);
     525          17 :         if (pszPOSTURL)
     526          17 :             osPostTransactionURL = pszPOSTURL;
     527             :     }
     528             : 
     529          19 :     psChild = psChild->psChild;
     530          62 :     while (psChild)
     531             :     {
     532         119 :         if (psChild->eType == CXT_Element &&
     533          52 :             strcmp(psChild->pszValue, "Parameter") == 0 &&
     534           6 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "idgen") == 0)
     535             :         {
     536           3 :             break;
     537             :         }
     538          43 :         psChild = psChild->psNext;
     539             :     }
     540          19 :     if (!psChild)
     541             :     {
     542          16 :         papszIdGenMethods = CSLAddString(nullptr, "GenerateNew");
     543          16 :         return true;
     544             :     }
     545             : 
     546           3 :     psChild = psChild->psChild;
     547          13 :     while (psChild)
     548             :     {
     549          10 :         if (psChild->eType == CXT_Element &&
     550           7 :             strcmp(psChild->pszValue, "Value") == 0)
     551             :         {
     552           7 :             const CPLXMLNode *psChild2 = psChild->psChild;
     553          14 :             while (psChild2)
     554             :             {
     555           7 :                 if (psChild2->eType == CXT_Text)
     556             :                 {
     557           7 :                     papszIdGenMethods =
     558           7 :                         CSLAddString(papszIdGenMethods, psChild2->pszValue);
     559             :                 }
     560           7 :                 psChild2 = psChild2->psNext;
     561             :             }
     562             :         }
     563          10 :         psChild = psChild->psNext;
     564             :     }
     565             : 
     566           3 :     return true;
     567             : }
     568             : 
     569             : /************************************************************************/
     570             : /*                      DetectSupportPagingWFS2()                       */
     571             : /************************************************************************/
     572             : 
     573          41 : bool OGRWFSDataSource::DetectSupportPagingWFS2(
     574             :     const CPLXMLNode *psGetCapabilitiesResponse,
     575             :     const CPLXMLNode *psConfigurationRoot)
     576             : {
     577          41 :     const char *pszPagingAllowed = CPLGetConfigOption(
     578             :         "OGR_WFS_PAGING_ALLOWED",
     579             :         CPLGetXMLValue(psConfigurationRoot, "PagingAllowed", nullptr));
     580          41 :     if (pszPagingAllowed != nullptr && !CPLTestBool(pszPagingAllowed))
     581           0 :         return false;
     582             : 
     583             :     const CPLXMLNode *psOperationsMetadata =
     584          41 :         CPLGetXMLNode(psGetCapabilitiesResponse, "OperationsMetadata");
     585          41 :     if (!psOperationsMetadata)
     586             :     {
     587           7 :         return false;
     588             :     }
     589             : 
     590          34 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     591          68 :     while (psChild)
     592             :     {
     593         198 :         if (psChild->eType == CXT_Element &&
     594          98 :             strcmp(psChild->pszValue, "Constraint") == 0 &&
     595          32 :             strcmp(CPLGetXMLValue(psChild, "name", ""),
     596             :                    "ImplementsResultPaging") == 0)
     597             :         {
     598          32 :             if (!EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE"))
     599             :             {
     600           0 :                 psChild = nullptr;
     601           0 :                 break;
     602             :             }
     603          32 :             break;
     604             :         }
     605          34 :         psChild = psChild->psNext;
     606             :     }
     607          34 :     if (!psChild)
     608             :     {
     609           2 :         CPLDebug("WFS", "No paging support");
     610           2 :         return false;
     611             :     }
     612             : 
     613          32 :     psChild = psOperationsMetadata->psChild;
     614          32 :     while (psChild)
     615             :     {
     616          96 :         if (psChild->eType == CXT_Element &&
     617          64 :             strcmp(psChild->pszValue, "Operation") == 0 &&
     618          32 :             strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0)
     619             :         {
     620          32 :             break;
     621             :         }
     622           0 :         psChild = psChild->psNext;
     623             :     }
     624             : 
     625          32 :     const char *pszPageSize = CPLGetConfigOption(
     626             :         "OGR_WFS_PAGE_SIZE",
     627             :         CPLGetXMLValue(psConfigurationRoot, "PageSize", nullptr));
     628          32 :     if (psChild && pszPageSize == nullptr)
     629             :     {
     630          30 :         psChild = psChild->psChild;
     631          64 :         while (psChild)
     632             :         {
     633         162 :             if (psChild->eType == CXT_Element &&
     634          94 :                 strcmp(psChild->pszValue, "Constraint") == 0 &&
     635          30 :                 strcmp(CPLGetXMLValue(psChild, "name", ""), "CountDefault") ==
     636             :                     0)
     637             :             {
     638          30 :                 int nVal = atoi(CPLGetXMLValue(psChild, "DefaultValue", "0"));
     639          30 :                 if (nVal > 0)
     640             :                 {
     641          30 :                     nPageSize = nVal;
     642             :                     const int nPageSizeURL =
     643          30 :                         atoi(CPLURLGetValue(osBaseURL, "COUNT"));
     644          30 :                     if (nPageSizeURL > 0 && nPageSizeURL < nPageSize)
     645             :                     {
     646           0 :                         nPageSize = nPageSizeURL;
     647             :                     }
     648             :                 }
     649             : 
     650          30 :                 break;
     651             :             }
     652          34 :             psChild = psChild->psNext;
     653             :         }
     654             :     }
     655          32 :     if (pszPageSize != nullptr)
     656             :     {
     657           2 :         nPageSize = atoi(pszPageSize);
     658           2 :         if (nPageSize <= 0)
     659           0 :             nPageSize = DEFAULT_PAGE_SIZE;
     660             :     }
     661             : 
     662          32 :     CPLDebug("WFS", "Paging support with page size %d", nPageSize);
     663          32 :     bPagingAllowed = true;
     664             : 
     665          32 :     return true;
     666             : }
     667             : 
     668             : /************************************************************************/
     669             : /*                   DetectSupportStandardJoinsWFS2()                   */
     670             : /************************************************************************/
     671             : 
     672          41 : bool OGRWFSDataSource::DetectSupportStandardJoinsWFS2(const CPLXMLNode *psRoot)
     673             : {
     674             :     const CPLXMLNode *psOperationsMetadata =
     675          41 :         CPLGetXMLNode(psRoot, "OperationsMetadata");
     676          41 :     if (!psOperationsMetadata)
     677             :     {
     678           7 :         return false;
     679             :     }
     680             : 
     681          34 :     const CPLXMLNode *psChild = psOperationsMetadata->psChild;
     682         100 :     while (psChild)
     683             :     {
     684         270 :         if (psChild->eType == CXT_Element &&
     685         146 :             strcmp(psChild->pszValue, "Constraint") == 0 &&
     686          56 :             strcmp(CPLGetXMLValue(psChild, "name", ""),
     687             :                    "ImplementsStandardJoins") == 0)
     688             :         {
     689          24 :             if (!EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE"))
     690             :             {
     691           0 :                 psChild = nullptr;
     692           0 :                 break;
     693             :             }
     694          24 :             break;
     695             :         }
     696          66 :         psChild = psChild->psNext;
     697             :     }
     698          34 :     if (!psChild)
     699             :     {
     700          10 :         CPLDebug("WFS", "No ImplementsStandardJoins support");
     701          10 :         return false;
     702             :     }
     703          24 :     bStandardJoinsWFS2 = true;
     704          24 :     return true;
     705             : }
     706             : 
     707             : /************************************************************************/
     708             : /*                       FindComparisonOperator()                       */
     709             : /************************************************************************/
     710             : 
     711         330 : static bool FindComparisonOperator(const CPLXMLNode *psNode, const char *pszVal)
     712             : {
     713         330 :     const CPLXMLNode *psChild = psNode->psChild;
     714        1615 :     while (psChild)
     715             :     {
     716        1599 :         if (psChild->eType == CXT_Element &&
     717        1599 :             strcmp(psChild->pszValue, "ComparisonOperator") == 0)
     718             :         {
     719        1599 :             if (strcmp(CPLGetXMLValue(psChild, nullptr, ""), pszVal) == 0)
     720         314 :                 return true;
     721             : 
     722             :             /* For WFS 2.0.0 */
     723        1285 :             const char *pszName = CPLGetXMLValue(psChild, "name", nullptr);
     724        1285 :             if (pszName != nullptr && STARTS_WITH(pszName, "PropertyIs") &&
     725           0 :                 strcmp(pszName + 10, pszVal) == 0)
     726           0 :                 return true;
     727             :         }
     728        1285 :         psChild = psChild->psNext;
     729             :     }
     730          16 :     return false;
     731             : }
     732             : 
     733             : /************************************************************************/
     734             : /*                            LoadFromFile()                            */
     735             : /************************************************************************/
     736             : 
     737         146 : CPLXMLNode *OGRWFSDataSource::LoadFromFile(const char *pszFilename)
     738             : {
     739             :     VSIStatBufL sStatBuf;
     740         146 :     if (VSIStatExL(pszFilename, &sStatBuf,
     741         157 :                    VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) != 0 ||
     742          11 :         VSI_ISDIR(sStatBuf.st_mode))
     743         135 :         return nullptr;
     744             : 
     745          11 :     VSILFILE *fp = VSIFOpenL(pszFilename, "rb");
     746             : 
     747          11 :     if (fp == nullptr)
     748           0 :         return nullptr;
     749             : 
     750             :     /* -------------------------------------------------------------------- */
     751             :     /*      It is the right file, now load the full XML definition.         */
     752             :     /* -------------------------------------------------------------------- */
     753          11 :     VSIFSeekL(fp, 0, SEEK_END);
     754          11 :     const auto nLenLarge = VSIFTellL(fp);
     755          11 :     VSIFSeekL(fp, 0, SEEK_SET);
     756          11 :     if (nLenLarge > 100 * 1024 * 1024)
     757             :     {
     758           0 :         VSIFCloseL(fp);
     759           0 :         return nullptr;
     760             :     }
     761          11 :     const int nLen = static_cast<int>(nLenLarge);
     762             : 
     763          11 :     char *pszXML = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen + 1));
     764          11 :     if (pszXML == nullptr)
     765             :     {
     766           0 :         VSIFCloseL(fp);
     767           0 :         return nullptr;
     768             :     }
     769          11 :     pszXML[nLen] = '\0';
     770          11 :     if (static_cast<int>(VSIFReadL(pszXML, 1, nLen, fp)) != nLen)
     771             :     {
     772           0 :         CPLFree(pszXML);
     773           0 :         VSIFCloseL(fp);
     774             : 
     775           0 :         return nullptr;
     776             :     }
     777          11 :     VSIFCloseL(fp);
     778             : 
     779          11 :     if (!STARTS_WITH_CI(pszXML, "<OGRWFSDataSource>") &&
     780           4 :         strstr(pszXML, "<WFS_Capabilities") == nullptr &&
     781           0 :         strstr(pszXML, "<wfs:WFS_Capabilities") == nullptr)
     782             :     {
     783           0 :         return nullptr;
     784             :     }
     785             : 
     786          11 :     if (strstr(pszXML, "CubeWerx"))
     787             :     {
     788             :         /* At least true for CubeWerx Suite 4.15.1 */
     789           0 :         bUseFeatureId = true;
     790             :     }
     791          11 :     else if (strstr(pszXML, "deegree"))
     792             :     {
     793           0 :         bGmlObjectIdNeedsGMLPrefix = true;
     794             :     }
     795             : 
     796          11 :     CPLXMLNode *psXML = CPLParseXMLString(pszXML);
     797          11 :     CPLFree(pszXML);
     798             : 
     799          11 :     return psXML;
     800             : }
     801             : 
     802             : /************************************************************************/
     803             : /*                        SendGetCapabilities()                         */
     804             : /************************************************************************/
     805             : 
     806         137 : CPLHTTPResult *OGRWFSDataSource::SendGetCapabilities(const char *pszBaseURL,
     807             :                                                      CPLString &osTypeName)
     808             : {
     809         274 :     CPLString osURL(pszBaseURL);
     810             : 
     811         137 :     osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
     812         137 :     osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities");
     813         137 :     osTypeName = CPLURLGetValue(osURL, "TYPENAME");
     814         137 :     if (osTypeName.empty())
     815         137 :         osTypeName = CPLURLGetValue(osURL, "TYPENAMES");
     816         137 :     osURL = CPLURLAddKVP(osURL, "TYPENAME", nullptr);
     817         137 :     osURL = CPLURLAddKVP(osURL, "TYPENAMES", nullptr);
     818         137 :     osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
     819         137 :     osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
     820         137 :     osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
     821         137 :     osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT", nullptr);
     822             : 
     823         137 :     CPLDebug("WFS", "%s", osURL.c_str());
     824             : 
     825         137 :     CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
     826         137 :     if (psResult == nullptr)
     827             :     {
     828           3 :         return nullptr;
     829             :     }
     830             : 
     831         134 :     if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
     832         133 :                "<ServiceExceptionReport") != nullptr ||
     833         133 :         strstr(reinterpret_cast<const char *>(psResult->pabyData),
     834         133 :                "<ows:ExceptionReport") != nullptr ||
     835         133 :         strstr(reinterpret_cast<const char *>(psResult->pabyData),
     836             :                "<ExceptionReport") != nullptr)
     837             :     {
     838           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s",
     839             :                  psResult->pabyData);
     840           1 :         CPLHTTPDestroyResult(psResult);
     841           1 :         return nullptr;
     842             :     }
     843             : 
     844         133 :     return psResult;
     845             : }
     846             : 
     847             : /************************************************************************/
     848             : /*                                Open()                                */
     849             : /************************************************************************/
     850             : 
     851         147 : int OGRWFSDataSource::Open(const char *pszFilename, int bUpdateIn,
     852             :                            CSLConstList papszOpenOptionsIn)
     853             : 
     854             : {
     855         147 :     bUpdate = CPL_TO_BOOL(bUpdateIn);
     856             : 
     857         147 :     const CPLXMLNode *psWFSCapabilities = nullptr;
     858         147 :     CPLXMLNode *psXML = nullptr;
     859         147 :     if (!STARTS_WITH(pszFilename, "http://") &&
     860         146 :         !STARTS_WITH(pszFilename, "https://"))
     861             :     {
     862         146 :         psXML = LoadFromFile(pszFilename);
     863             :     }
     864         294 :     CPLString osTypeName;
     865         147 :     const char *pszBaseURL = nullptr;
     866             : 
     867         147 :     bEmptyAsNull = CPLFetchBool(papszOpenOptionsIn, "EMPTY_AS_NULL", true);
     868             : 
     869         147 :     const CPLXMLNode *psConfigurationRoot = nullptr;
     870             : 
     871         147 :     if (psXML == nullptr)
     872             :     {
     873         276 :         if (!STARTS_WITH_CI(pszFilename, "WFS:") &&
     874           2 :             !STARTS_WITH(pszFilename, "http://") &&
     875         140 :             !STARTS_WITH(pszFilename, "https://") &&
     876           1 :             FindSubStringInsensitive(pszFilename, "SERVICE=WFS") == nullptr)
     877             :         {
     878           6 :             return FALSE;
     879             :         }
     880             : 
     881         136 :         pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL");
     882         136 :         if (pszBaseURL == nullptr)
     883             :         {
     884         136 :             pszBaseURL = pszFilename;
     885         136 :             if (STARTS_WITH_CI(pszFilename, "WFS:"))
     886         135 :                 pszBaseURL += strlen("WFS:");
     887             :         }
     888             : 
     889         136 :         osBaseURL = pszBaseURL;
     890             : 
     891         136 :         if (!STARTS_WITH(pszBaseURL, "http://") &&
     892         133 :             !STARTS_WITH(pszBaseURL, "https://") &&
     893         133 :             !STARTS_WITH(pszBaseURL, "/vsimem/"))
     894           0 :             return FALSE;
     895             : 
     896         136 :         CPLString strOriginalTypeName = "";
     897             :         CPLHTTPResult *psResult =
     898         136 :             SendGetCapabilities(pszBaseURL, strOriginalTypeName);
     899         136 :         osTypeName = WFS_DecodeURL(strOriginalTypeName);
     900         136 :         if (psResult == nullptr)
     901             :         {
     902           4 :             return FALSE;
     903             :         }
     904             : 
     905         132 :         if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
     906             :                    "CubeWerx"))
     907             :         {
     908             :             /* At least true for CubeWerx Suite 4.15.1 */
     909           0 :             bUseFeatureId = true;
     910             :         }
     911         132 :         else if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
     912             :                         "deegree"))
     913             :         {
     914           0 :             bGmlObjectIdNeedsGMLPrefix = true;
     915             :         }
     916             : 
     917         264 :         psXML = CPLParseXMLString(
     918         132 :             reinterpret_cast<const char *>(psResult->pabyData));
     919         132 :         if (psXML == nullptr)
     920             :         {
     921           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
     922             :                      psResult->pabyData);
     923           1 :             CPLHTTPDestroyResult(psResult);
     924           1 :             return FALSE;
     925             :         }
     926         131 :         osGetCapabilities = reinterpret_cast<const char *>(psResult->pabyData);
     927             : 
     928         131 :         CPLHTTPDestroyResult(psResult);
     929             :     }
     930          13 :     else if (WFSFindNode(psXML, "OGRWFSDataSource") == nullptr &&
     931           3 :              WFSFindNode(psXML, "WFS_Capabilities") != nullptr)
     932             :     {
     933             :         /* This is directly the Capabilities document */
     934             :         char *pszXML =
     935           3 :             CPLSerializeXMLTree(WFSFindNode(psXML, "WFS_Capabilities"));
     936           3 :         osGetCapabilities = pszXML;
     937           3 :         CPLFree(pszXML);
     938             :     }
     939             :     else
     940             :     {
     941           7 :         psConfigurationRoot = WFSFindNode(psXML, "OGRWFSDataSource");
     942           7 :         if (psConfigurationRoot == nullptr)
     943             :         {
     944           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     945             :                      "Cannot find <OGRWFSDataSource>");
     946           0 :             CPLDestroyXMLNode(psXML);
     947           1 :             return FALSE;
     948             :         }
     949             : 
     950           7 :         pszBaseURL = CPLGetXMLValue(psConfigurationRoot, "URL", nullptr);
     951           7 :         if (pszBaseURL == nullptr)
     952             :         {
     953           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <URL>");
     954           0 :             CPLDestroyXMLNode(psXML);
     955           0 :             return FALSE;
     956             :         }
     957           7 :         osBaseURL = pszBaseURL;
     958             : 
     959             :         /* --------------------------------------------------------------------
     960             :          */
     961             :         /*      Capture other parameters. */
     962             :         /* --------------------------------------------------------------------
     963             :          */
     964             :         const char *pszParam =
     965           7 :             CPLGetXMLValue(psConfigurationRoot, "Timeout", nullptr);
     966           7 :         if (pszParam)
     967           0 :             papszHttpOptions =
     968           0 :                 CSLSetNameValue(papszHttpOptions, "TIMEOUT", pszParam);
     969             : 
     970           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "HTTPAUTH", nullptr);
     971           7 :         if (pszParam)
     972           0 :             papszHttpOptions =
     973           0 :                 CSLSetNameValue(papszHttpOptions, "HTTPAUTH", pszParam);
     974             : 
     975           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "USERPWD", nullptr);
     976           7 :         if (pszParam)
     977           0 :             papszHttpOptions =
     978           0 :                 CSLSetNameValue(papszHttpOptions, "USERPWD", pszParam);
     979             : 
     980           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "COOKIE", nullptr);
     981           7 :         if (pszParam)
     982           0 :             papszHttpOptions =
     983           0 :                 CSLSetNameValue(papszHttpOptions, "COOKIE", pszParam);
     984             : 
     985           7 :         pszParam = CPLGetXMLValue(psConfigurationRoot, "Version", nullptr);
     986           7 :         if (pszParam)
     987           0 :             osVersion = pszParam;
     988             : 
     989             :         pszParam =
     990           7 :             CPLGetXMLValue(psConfigurationRoot, "BaseStartIndex", nullptr);
     991           7 :         if (pszParam)
     992           0 :             nBaseStartIndex = atoi(pszParam);
     993             : 
     994           7 :         CPLString strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAME");
     995           7 :         if (strOriginalTypeName.empty())
     996           7 :             strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAMES");
     997           7 :         osTypeName = WFS_DecodeURL(strOriginalTypeName);
     998             : 
     999             :         psWFSCapabilities =
    1000           7 :             WFSFindNode(psConfigurationRoot, "WFS_Capabilities");
    1001           7 :         if (psWFSCapabilities == nullptr)
    1002             :         {
    1003             :             CPLHTTPResult *psResult =
    1004           1 :                 SendGetCapabilities(pszBaseURL, strOriginalTypeName);
    1005           1 :             if (psResult == nullptr)
    1006             :             {
    1007           0 :                 CPLDestroyXMLNode(psXML);
    1008           0 :                 return FALSE;
    1009             :             }
    1010             : 
    1011           2 :             CPLXMLNode *psXML2 = CPLParseXMLString(
    1012           1 :                 reinterpret_cast<const char *>(psResult->pabyData));
    1013           1 :             if (psXML2 == nullptr)
    1014             :             {
    1015           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1016             :                          "Invalid XML content : %s", psResult->pabyData);
    1017           0 :                 CPLHTTPDestroyResult(psResult);
    1018           0 :                 CPLDestroyXMLNode(psXML);
    1019           0 :                 return FALSE;
    1020             :             }
    1021             : 
    1022           1 :             CPLHTTPDestroyResult(psResult);
    1023             : 
    1024           1 :             psWFSCapabilities = WFSFindNode(psXML2, "WFS_Capabilities");
    1025           1 :             if (psWFSCapabilities == nullptr)
    1026             :             {
    1027           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1028             :                          "Cannot find <WFS_Capabilities>");
    1029           0 :                 CPLDestroyXMLNode(psXML);
    1030           0 :                 CPLDestroyXMLNode(psXML2);
    1031           0 :                 return FALSE;
    1032             :             }
    1033             : 
    1034           1 :             CPLAddXMLChild(psXML, CPLCloneXMLTree(psWFSCapabilities));
    1035             : 
    1036             :             const bool bOK =
    1037           1 :                 CPL_TO_BOOL(CPLSerializeXMLTreeToFile(psXML, pszFilename));
    1038             : 
    1039           1 :             CPLDestroyXMLNode(psXML);
    1040           1 :             CPLDestroyXMLNode(psXML2);
    1041             : 
    1042           1 :             if (bOK)
    1043           1 :                 return Open(pszFilename, bUpdate, papszOpenOptionsIn);
    1044             : 
    1045           0 :             return FALSE;
    1046             :         }
    1047             :         else
    1048             :         {
    1049           6 :             psFileXML = psXML;
    1050             : 
    1051             :             /* To avoid to have nodes after WFSCapabilities */
    1052           6 :             CPLXMLNode *psAfterWFSCapabilities = psWFSCapabilities->psNext;
    1053           6 :             const_cast<CPLXMLNode *>(psWFSCapabilities)->psNext = nullptr;
    1054           6 :             char *pszXML = CPLSerializeXMLTree(psWFSCapabilities);
    1055           6 :             const_cast<CPLXMLNode *>(psWFSCapabilities)->psNext =
    1056             :                 psAfterWFSCapabilities;
    1057           6 :             osGetCapabilities = pszXML;
    1058           6 :             CPLFree(pszXML);
    1059             :         }
    1060             :     }
    1061             : 
    1062         140 :     bInvertAxisOrderIfLatLong = CPLTestBool(CSLFetchNameValueDef(
    1063             :         papszOpenOptionsIn, "INVERT_AXIS_ORDER_IF_LAT_LONG",
    1064             :         CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")));
    1065             :     osConsiderEPSGAsURN = CSLFetchNameValueDef(
    1066             :         papszOpenOptionsIn, "CONSIDER_EPSG_AS_URN",
    1067         140 :         CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", "AUTO"));
    1068         140 :     bExposeGMLId = CPLTestBool(
    1069             :         CSLFetchNameValueDef(papszOpenOptionsIn, "EXPOSE_GML_ID",
    1070             :                              CPLGetConfigOption("GML_EXPOSE_GML_ID", "YES")));
    1071             : 
    1072         140 :     CPLXMLNode *psStrippedXML = CPLCloneXMLTree(psXML);
    1073         140 :     CPLStripXMLNamespace(psStrippedXML, nullptr, TRUE);
    1074         140 :     psWFSCapabilities = CPLGetXMLNode(psStrippedXML, "=WFS_Capabilities");
    1075         140 :     if (psWFSCapabilities == nullptr)
    1076             :     {
    1077             :         psWFSCapabilities =
    1078           8 :             CPLGetXMLNode(psStrippedXML, "=OGRWFSDataSource.WFS_Capabilities");
    1079             :     }
    1080         140 :     if (psWFSCapabilities == nullptr)
    1081             :     {
    1082           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <WFS_Capabilities>");
    1083           2 :         if (!psFileXML)
    1084           2 :             CPLDestroyXMLNode(psXML);
    1085           2 :         CPLDestroyXMLNode(psStrippedXML);
    1086           2 :         return FALSE;
    1087             :     }
    1088             : 
    1089         138 :     if (pszBaseURL == nullptr)
    1090             :     {
    1091             :         /* This is directly the Capabilities document */
    1092           2 :         pszBaseURL = CPLGetXMLValue(
    1093             :             psWFSCapabilities, "OperationsMetadata.Operation.DCP.HTTP.Get.href",
    1094             :             nullptr);
    1095           2 :         if (pszBaseURL == nullptr) /* WFS 1.0.0 variant */
    1096           1 :             pszBaseURL = CPLGetXMLValue(psWFSCapabilities,
    1097             :                                         "Capability.Request.GetCapabilities."
    1098             :                                         "DCPType.HTTP.Get.onlineResource",
    1099             :                                         nullptr);
    1100             : 
    1101           2 :         if (pszBaseURL == nullptr)
    1102             :         {
    1103           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find base URL");
    1104           1 :             if (!psFileXML)
    1105           1 :                 CPLDestroyXMLNode(psXML);
    1106           1 :             CPLDestroyXMLNode(psStrippedXML);
    1107           1 :             return FALSE;
    1108             :         }
    1109             : 
    1110           1 :         osBaseURL = pszBaseURL;
    1111             :     }
    1112             : 
    1113         137 :     pszBaseURL = nullptr;
    1114             : 
    1115         822 :     for (const auto &sMetadata : asMetadata)
    1116             :     {
    1117             :         const char *pszVal =
    1118         685 :             CPLGetXMLValue(psWFSCapabilities, sMetadata.pszPath, nullptr);
    1119         685 :         if (pszVal)
    1120          13 :             SetMetadataItem(sMetadata.pszMDI, pszVal);
    1121             :     }
    1122             : 
    1123         137 :     if (osVersion.empty())
    1124         137 :         osVersion = CPLGetXMLValue(psWFSCapabilities, "version", "1.0.0");
    1125         137 :     if (strcmp(osVersion.c_str(), "1.0.0") == 0)
    1126             :     {
    1127           1 :         bUseFeatureId = true;
    1128             :     }
    1129             :     else
    1130             :     {
    1131             :         /* Some servers happen to support RESULTTYPE=hits in 1.0.0, but there */
    1132             :         /* is no way to advertises this */
    1133         136 :         if (atoi(osVersion) >= 2)
    1134          41 :             bGetFeatureSupportHits = true; /* WFS >= 2.0.0 supports hits */
    1135             :         else
    1136          95 :             bGetFeatureSupportHits =
    1137          95 :                 DetectIfGetFeatureSupportHits(psWFSCapabilities);
    1138         136 :         bRequiresEnvelopeSpatialFilter =
    1139         136 :             DetectRequiresEnvelopeSpatialFilter(psWFSCapabilities);
    1140             :     }
    1141             : 
    1142         137 :     bool bRequestJSON = false;
    1143             : 
    1144         137 :     if (atoi(osVersion) >= 2)
    1145             :     {
    1146          82 :         CPLString osMaxFeatures = CPLURLGetValue(osBaseURL, "COUNT");
    1147             :         /* Ok, people are used to MAXFEATURES, so be nice to recognize it if it
    1148             :          * is used for WFS 2.0 ... */
    1149          41 :         if (osMaxFeatures.empty())
    1150             :         {
    1151          41 :             osMaxFeatures = CPLURLGetValue(osBaseURL, "MAXFEATURES");
    1152          41 :             if (!osMaxFeatures.empty() &&
    1153           0 :                 CPLTestBool(
    1154             :                     CPLGetConfigOption("OGR_WFS_FIX_MAXFEATURES", "YES")))
    1155             :             {
    1156           0 :                 CPLDebug("WFS", "MAXFEATURES wrongly used for WFS 2.0. Using "
    1157             :                                 "COUNT instead");
    1158           0 :                 osBaseURL = CPLURLAddKVP(osBaseURL, "MAXFEATURES", nullptr);
    1159           0 :                 osBaseURL = CPLURLAddKVP(osBaseURL, "COUNT", osMaxFeatures);
    1160             :             }
    1161             :         }
    1162             : 
    1163          41 :         DetectSupportPagingWFS2(psWFSCapabilities, psConfigurationRoot);
    1164          41 :         DetectSupportStandardJoinsWFS2(psWFSCapabilities);
    1165             : 
    1166             :         const CPLXMLNode *psOperationsMetadata =
    1167          41 :             CPLGetXMLNode(psWFSCapabilities, "OperationsMetadata");
    1168          41 :         auto poGMLDriver = GDALDriver::FromHandle(GDALGetDriverByName("GML"));
    1169          41 :         if (psOperationsMetadata && !(poGMLDriver && poGMLDriver->pfnOpen) &&
    1170          41 :             CPLURLGetValue(osBaseURL, "OUTPUTFORMAT").empty())
    1171             :         {
    1172           0 :             const CPLXMLNode *psChild = psOperationsMetadata->psChild;
    1173           0 :             while (psChild)
    1174             :             {
    1175           0 :                 if (psChild->eType == CXT_Element &&
    1176           0 :                     strcmp(psChild->pszValue, "Operation") == 0 &&
    1177           0 :                     strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") ==
    1178             :                         0)
    1179             :                 {
    1180           0 :                     break;
    1181             :                 }
    1182           0 :                 psChild = psChild->psNext;
    1183             :             }
    1184           0 :             if (psChild)
    1185             :             {
    1186           0 :                 psChild = psChild->psChild;
    1187           0 :                 while (psChild)
    1188             :                 {
    1189           0 :                     if (psChild->eType == CXT_Element &&
    1190           0 :                         strcmp(psChild->pszValue, "Parameter") == 0 &&
    1191           0 :                         strcmp(CPLGetXMLValue(psChild, "name", ""),
    1192             :                                "outputFormat") == 0)
    1193             :                     {
    1194           0 :                         break;
    1195             :                     }
    1196           0 :                     psChild = psChild->psNext;
    1197             :                 }
    1198           0 :                 if (psChild)
    1199             :                 {
    1200             :                     const CPLXMLNode *psAllowedValues =
    1201           0 :                         CPLGetXMLNode(psChild, "AllowedValues");
    1202           0 :                     if (psAllowedValues)
    1203             :                     {
    1204           0 :                         psChild = psAllowedValues->psChild;
    1205           0 :                         while (psChild)
    1206             :                         {
    1207           0 :                             if (psChild->eType == CXT_Element &&
    1208           0 :                                 strcmp(psChild->pszValue, "Value") == 0 &&
    1209           0 :                                 psChild->psChild &&
    1210           0 :                                 psChild->psChild->eType == CXT_Text)
    1211             :                             {
    1212           0 :                                 CPLDebug("WFS", "Available output format: %s",
    1213           0 :                                          psChild->psChild->pszValue);
    1214           0 :                                 if (strcmp(psChild->psChild->pszValue,
    1215             :                                            "json") == 0)
    1216             :                                 {
    1217           0 :                                     bRequestJSON = true;
    1218             :                                 }
    1219             :                             }
    1220           0 :                             psChild = psChild->psNext;
    1221             :                         }
    1222             :                     }
    1223             :                 }
    1224             :             }
    1225             :         }
    1226             :     }
    1227             : 
    1228         137 :     DetectTransactionSupport(psWFSCapabilities);
    1229             : 
    1230         137 :     if (bUpdate && !bTransactionSupport)
    1231             :     {
    1232           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1233             :                  "Server is read-only WFS; no WFS-T feature advertized");
    1234           1 :         if (!psFileXML)
    1235           1 :             CPLDestroyXMLNode(psXML);
    1236           1 :         CPLDestroyXMLNode(psStrippedXML);
    1237           1 :         return FALSE;
    1238             :     }
    1239             : 
    1240         136 :     const CPLXMLNode *psFilterCap = CPLGetXMLNode(
    1241             :         psWFSCapabilities, "Filter_Capabilities.Scalar_Capabilities");
    1242         136 :     if (psFilterCap)
    1243             :     {
    1244          47 :         bHasMinOperators =
    1245          53 :             CPLGetXMLNode(psFilterCap, "LogicalOperators") != nullptr ||
    1246           6 :             CPLGetXMLNode(psFilterCap, "Logical_Operators") != nullptr;
    1247          47 :         if (CPLGetXMLNode(psFilterCap, "ComparisonOperators"))
    1248          41 :             psFilterCap = CPLGetXMLNode(psFilterCap, "ComparisonOperators");
    1249           6 :         else if (CPLGetXMLNode(psFilterCap, "Comparison_Operators"))
    1250           0 :             psFilterCap = CPLGetXMLNode(psFilterCap, "Comparison_Operators");
    1251             :         else
    1252           6 :             psFilterCap = nullptr;
    1253          47 :         if (psFilterCap)
    1254             :         {
    1255          41 :             if (CPLGetXMLNode(psFilterCap, "Simple_Comparisons") == nullptr)
    1256             :             {
    1257          41 :                 bHasMinOperators &=
    1258          41 :                     FindComparisonOperator(psFilterCap, "LessThan");
    1259          41 :                 bHasMinOperators &=
    1260          41 :                     FindComparisonOperator(psFilterCap, "GreaterThan");
    1261          41 :                 if (atoi(osVersion) >= 2)
    1262             :                 {
    1263           6 :                     bHasMinOperators &= FindComparisonOperator(
    1264           6 :                         psFilterCap, "LessThanOrEqualTo");
    1265           6 :                     bHasMinOperators &= FindComparisonOperator(
    1266           6 :                         psFilterCap, "GreaterThanOrEqualTo");
    1267             :                 }
    1268             :                 else
    1269             :                 {
    1270          35 :                     bHasMinOperators &=
    1271          35 :                         FindComparisonOperator(psFilterCap, "LessThanEqualTo");
    1272          35 :                     bHasMinOperators &= FindComparisonOperator(
    1273          35 :                         psFilterCap, "GreaterThanEqualTo");
    1274             :                 }
    1275          41 :                 bHasMinOperators &=
    1276          41 :                     FindComparisonOperator(psFilterCap, "EqualTo");
    1277          41 :                 bHasMinOperators &=
    1278          41 :                     FindComparisonOperator(psFilterCap, "NotEqualTo");
    1279          41 :                 bHasMinOperators &= FindComparisonOperator(psFilterCap, "Like");
    1280             :             }
    1281             :             else
    1282             :             {
    1283           0 :                 bHasMinOperators &=
    1284           0 :                     CPLGetXMLNode(psFilterCap, "Simple_Comparisons") !=
    1285           0 :                         nullptr &&
    1286           0 :                     CPLGetXMLNode(psFilterCap, "Like") != nullptr;
    1287             :             }
    1288          41 :             bHasNullCheck =
    1289          41 :                 FindComparisonOperator(psFilterCap, "NullCheck") ||
    1290          43 :                 FindComparisonOperator(psFilterCap, "Null") || /* WFS 2.0.0 */
    1291           2 :                 CPLGetXMLNode(psFilterCap, "NullCheck") != nullptr;
    1292             :         }
    1293             :         else
    1294             :         {
    1295           6 :             bHasMinOperators = false;
    1296             :         }
    1297             :     }
    1298             : 
    1299             :     const CPLXMLNode *psChild =
    1300         136 :         CPLGetXMLNode(psWFSCapabilities, "FeatureTypeList");
    1301         136 :     if (psChild == nullptr)
    1302             :     {
    1303           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <FeatureTypeList>");
    1304           1 :         if (!psFileXML)
    1305           1 :             CPLDestroyXMLNode(psXML);
    1306           1 :         CPLDestroyXMLNode(psStrippedXML);
    1307           1 :         return FALSE;
    1308             :     }
    1309             : 
    1310             :     /* Check if there are layer names whose identical except their prefix */
    1311         135 :     std::set<CPLString> aosSetLayerNames;
    1312         369 :     for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr;
    1313         234 :          psChildIter = psChildIter->psNext)
    1314             :     {
    1315         236 :         if (psChildIter->eType == CXT_Element &&
    1316         236 :             strcmp(psChildIter->pszValue, "FeatureType") == 0)
    1317             :         {
    1318             :             const char *l_pszName =
    1319         231 :                 CPLGetXMLValue(psChildIter, "Name", nullptr);
    1320         231 :             if (l_pszName != nullptr)
    1321             :             {
    1322         213 :                 const char *pszShortName = strchr(l_pszName, ':');
    1323         213 :                 if (pszShortName)
    1324          35 :                     l_pszName = pszShortName + 1;
    1325         213 :                 if (aosSetLayerNames.find(l_pszName) != aosSetLayerNames.end())
    1326             :                 {
    1327           2 :                     bKeepLayerNamePrefix = true;
    1328           2 :                     CPLDebug("WFS",
    1329             :                              "At least 2 layers have names that are only "
    1330             :                              "distinguishable by keeping the prefix");
    1331           2 :                     break;
    1332             :                 }
    1333         211 :                 aosSetLayerNames.insert(l_pszName);
    1334             :             }
    1335             :         }
    1336             :     }
    1337             : 
    1338         135 :     char **papszTypenames = nullptr;
    1339         135 :     if (!osTypeName.empty())
    1340             :         papszTypenames =
    1341           0 :             CSLTokenizeStringComplex(osTypeName, ",", FALSE, FALSE);
    1342             : 
    1343         371 :     for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr;
    1344         236 :          psChildIter = psChildIter->psNext)
    1345             :     {
    1346         236 :         if (psChildIter->eType == CXT_Element &&
    1347         236 :             strcmp(psChildIter->pszValue, "FeatureType") == 0)
    1348             :         {
    1349         231 :             const char *pszNS = nullptr;
    1350         231 :             const char *pszNSVal = nullptr;
    1351         231 :             CPLXMLNode *psFeatureTypeIter = psChildIter->psChild;
    1352         958 :             while (psFeatureTypeIter != nullptr)
    1353             :             {
    1354         727 :                 if (psFeatureTypeIter->eType == CXT_Attribute)
    1355             :                 {
    1356          28 :                     pszNS = psFeatureTypeIter->pszValue;
    1357          28 :                     pszNSVal = psFeatureTypeIter->psChild->pszValue;
    1358             :                 }
    1359         727 :                 psFeatureTypeIter = psFeatureTypeIter->psNext;
    1360             :             }
    1361             : 
    1362             :             const char *l_pszName =
    1363         231 :                 CPLGetXMLValue(psChildIter, "Name", nullptr);
    1364             :             const char *pszTitle =
    1365         231 :                 CPLGetXMLValue(psChildIter, "Title", nullptr);
    1366             :             const char *pszAbstract =
    1367         231 :                 CPLGetXMLValue(psChildIter, "Abstract", nullptr);
    1368         231 :             if (l_pszName != nullptr &&
    1369           0 :                 (papszTypenames == nullptr ||
    1370           0 :                  CSLFindString(papszTypenames, l_pszName) != -1))
    1371             :             {
    1372             :                 const char *pszDefaultSRS =
    1373         213 :                     CPLGetXMLValue(psChildIter, "DefaultSRS", nullptr);
    1374         213 :                 if (pszDefaultSRS == nullptr)
    1375          18 :                     pszDefaultSRS = CPLGetXMLValue(psChildIter, "SRS", nullptr);
    1376         213 :                 if (pszDefaultSRS == nullptr)
    1377          18 :                     pszDefaultSRS = CPLGetXMLValue(psChildIter, "DefaultCRS",
    1378             :                                                    nullptr); /* WFS 2.0.0 */
    1379             : 
    1380             :                 const CPLXMLNode *psOtherSRS =
    1381         213 :                     CPLGetXMLNode(psChildIter, "OtherSRS");  // WFS 1.1
    1382         213 :                 if (psOtherSRS == nullptr)
    1383             :                     psOtherSRS =
    1384         212 :                         CPLGetXMLNode(psChildIter, "OtherCRS");  // WFS 2.0
    1385             : 
    1386         199 :                 const auto IsValidCRSName = [](const char *pszStr)
    1387             :                 {
    1388             :                     // EPSG:404000 is a GeoServer joke to indicate a unknown SRS
    1389             :                     // https://osgeo-org.atlassian.net/browse/GEOS-8993
    1390         398 :                     return !EQUAL(pszStr, "EPSG:404000") &&
    1391         398 :                            !EQUAL(pszStr, "urn:ogc:def:crs:EPSG::404000");
    1392             :                 };
    1393             : 
    1394         213 :                 if (pszDefaultSRS && !IsValidCRSName(pszDefaultSRS))
    1395           1 :                     pszDefaultSRS = nullptr;
    1396             : 
    1397         426 :                 std::vector<std::string> aosSupportedCRSList{};
    1398         426 :                 OGRLayer::GetSupportedSRSListRetType apoSupportedCRSList;
    1399         213 :                 if (psOtherSRS)
    1400             :                 {
    1401           2 :                     if (pszDefaultSRS)
    1402             :                     {
    1403             :                         auto poSRS =
    1404             :                             std::unique_ptr<OGRSpatialReference,
    1405             :                                             OGRSpatialReferenceReleaser>(
    1406           2 :                                 new OGRSpatialReference());
    1407           1 :                         if (poSRS->SetFromUserInput(
    1408             :                                 pszDefaultSRS,
    1409             :                                 OGRSpatialReference::
    1410           1 :                                     SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1411             :                             OGRERR_NONE)
    1412             :                         {
    1413           1 :                             aosSupportedCRSList.emplace_back(pszDefaultSRS);
    1414           1 :                             apoSupportedCRSList.emplace_back(std::move(poSRS));
    1415             :                         }
    1416             :                     }
    1417             : 
    1418             :                     CPLErrorStateBackuper oErrorStateBackuper(
    1419           4 :                         CPLQuietErrorHandler);
    1420           8 :                     for (const CPLXMLNode *psIter = psOtherSRS; psIter;
    1421           6 :                          psIter = psIter->psNext)
    1422             :                     {
    1423           6 :                         if (psIter->eType == CXT_Element)
    1424             :                         {
    1425             :                             const char *pszSRS =
    1426           6 :                                 CPLGetXMLValue(psIter, "", nullptr);
    1427           6 :                             if (pszSRS && IsValidCRSName(pszSRS))
    1428             :                             {
    1429             :                                 auto poSRS = std::unique_ptr<
    1430             :                                     OGRSpatialReference,
    1431             :                                     OGRSpatialReferenceReleaser>(
    1432           6 :                                     new OGRSpatialReference());
    1433           6 :                                 if (poSRS->SetFromUserInput(
    1434           3 :                                         EQUAL(pszSRS, "CRS:84") ? "OGC:CRS84"
    1435             :                                                                 : pszSRS,
    1436             :                                         OGRSpatialReference::
    1437           3 :                                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1438             :                                     OGRERR_NONE)
    1439             :                                 {
    1440           3 :                                     aosSupportedCRSList.emplace_back(pszSRS);
    1441             :                                     apoSupportedCRSList.emplace_back(
    1442           3 :                                         std::move(poSRS));
    1443             :                                 }
    1444             :                                 else
    1445             :                                 {
    1446           0 :                                     CPLDebug("WFS", "Invalid CRS %s", pszSRS);
    1447             :                                 }
    1448             :                             }
    1449             :                         }
    1450             :                     }
    1451             :                 }
    1452             : 
    1453             :                 CPLXMLNode *psOutputFormats =
    1454         213 :                     CPLGetXMLNode(psChildIter, "OutputFormats");
    1455         426 :                 CPLString osOutputFormat;
    1456         213 :                 if (psOutputFormats)
    1457             :                 {
    1458          16 :                     std::vector<CPLString> osFormats;
    1459           8 :                     CPLXMLNode *psOutputFormatIter = psOutputFormats->psChild;
    1460          16 :                     while (psOutputFormatIter)
    1461             :                     {
    1462           8 :                         if (psOutputFormatIter->eType == CXT_Element &&
    1463           8 :                             EQUAL(psOutputFormatIter->pszValue, "Format") &&
    1464           8 :                             psOutputFormatIter->psChild != nullptr &&
    1465           8 :                             psOutputFormatIter->psChild->eType == CXT_Text)
    1466             :                         {
    1467           8 :                             osFormats.push_back(
    1468           8 :                                 psOutputFormatIter->psChild->pszValue);
    1469             :                         }
    1470           8 :                         psOutputFormatIter = psOutputFormatIter->psNext;
    1471             :                     }
    1472             : 
    1473          16 :                     if (strcmp(osVersion.c_str(), "1.1.0") == 0 &&
    1474           8 :                         !osFormats.empty())
    1475             :                     {
    1476           8 :                         bool bFoundGML31 = false;
    1477           8 :                         for (size_t i = 0; i < osFormats.size(); i++)
    1478             :                         {
    1479           8 :                             if (strstr(osFormats[i].c_str(), "3.1") != nullptr)
    1480             :                             {
    1481           8 :                                 bFoundGML31 = true;
    1482           8 :                                 break;
    1483             :                             }
    1484             :                         }
    1485             : 
    1486             :                         /* If we didn't find any mention to GML 3.1, then
    1487             :                          * arbitrarily */
    1488             :                         /* use the first output format */
    1489           8 :                         if (!bFoundGML31)
    1490           0 :                             osOutputFormat = osFormats[0].c_str();
    1491             :                     }
    1492             :                 }
    1493         213 :                 if (osOutputFormat.empty() && bRequestJSON)
    1494           0 :                     osOutputFormat = "json";
    1495             : 
    1496         213 :                 OGRSpatialReference *poSRS = nullptr;
    1497         213 :                 bool bAxisOrderAlreadyInverted = false;
    1498             : 
    1499             :                 /* If a SRSNAME parameter has been encoded in the URL, use it as
    1500             :                  * the SRS */
    1501         426 :                 CPLString osSRSName = CPLURLGetValue(osBaseURL, "SRSNAME");
    1502         213 :                 if (!osSRSName.empty())
    1503             :                 {
    1504           0 :                     pszDefaultSRS = osSRSName.c_str();
    1505             :                 }
    1506             : 
    1507         213 :                 if (pszDefaultSRS)
    1508             :                 {
    1509         388 :                     OGRSpatialReference oSRS;
    1510         194 :                     if (oSRS.SetFromUserInput(
    1511             :                             pszDefaultSRS,
    1512             :                             OGRSpatialReference::
    1513         194 :                                 SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1514             :                         OGRERR_NONE)
    1515             :                     {
    1516         194 :                         poSRS = oSRS.Clone();
    1517         194 :                         poSRS->SetAxisMappingStrategy(
    1518         194 :                             bInvertAxisOrderIfLatLong
    1519             :                                 ? OAMS_TRADITIONAL_GIS_ORDER
    1520             :                                 : OAMS_AUTHORITY_COMPLIANT);
    1521         361 :                         if (GML_IsSRSLatLongOrder(pszDefaultSRS) &&
    1522         167 :                             bInvertAxisOrderIfLatLong)
    1523             :                         {
    1524         167 :                             bAxisOrderAlreadyInverted = true;
    1525             :                         }
    1526             :                     }
    1527             :                 }
    1528             : 
    1529         213 :                 CPLXMLNode *psBBox = nullptr;
    1530         213 :                 CPLXMLNode *psLatLongBBox = nullptr;
    1531             :                 /* bool bFoundBBox = false; */
    1532         213 :                 double dfMinX = 0.0;
    1533         213 :                 double dfMinY = 0.0;
    1534         213 :                 double dfMaxX = 0.0;
    1535         213 :                 double dfMaxY = 0.0;
    1536         213 :                 if ((psBBox = CPLGetXMLNode(psChildIter, "WGS84BoundingBox")) !=
    1537             :                     nullptr)
    1538             :                 {
    1539             :                     const char *pszLC =
    1540         195 :                         CPLGetXMLValue(psBBox, "LowerCorner", nullptr);
    1541             :                     const char *pszUC =
    1542         195 :                         CPLGetXMLValue(psBBox, "UpperCorner", nullptr);
    1543         195 :                     if (pszLC != nullptr && pszUC != nullptr)
    1544             :                     {
    1545         390 :                         CPLString osConcat(pszLC);
    1546         195 :                         osConcat += " ";
    1547         195 :                         osConcat += pszUC;
    1548         195 :                         char **papszTokens = CSLTokenizeStringComplex(
    1549             :                             osConcat, " ,", FALSE, FALSE);
    1550         195 :                         if (CSLCount(papszTokens) == 4)
    1551             :                         {
    1552             :                             // bFoundBBox = true;
    1553         195 :                             dfMinX = CPLAtof(papszTokens[0]);
    1554         195 :                             dfMinY = CPLAtof(papszTokens[1]);
    1555         195 :                             dfMaxX = CPLAtof(papszTokens[2]);
    1556         195 :                             dfMaxY = CPLAtof(papszTokens[3]);
    1557             :                         }
    1558         195 :                         CSLDestroy(papszTokens);
    1559             :                     }
    1560             :                 }
    1561          18 :                 else if ((psLatLongBBox = CPLGetXMLNode(
    1562          18 :                               psChildIter, "LatLongBoundingBox")) != nullptr)
    1563             :                 {
    1564             :                     const char *pszMinX =
    1565           0 :                         CPLGetXMLValue(psLatLongBBox, "minx", nullptr);
    1566             :                     const char *pszMinY =
    1567           0 :                         CPLGetXMLValue(psLatLongBBox, "miny", nullptr);
    1568             :                     const char *pszMaxX =
    1569           0 :                         CPLGetXMLValue(psLatLongBBox, "maxx", nullptr);
    1570             :                     const char *pszMaxY =
    1571           0 :                         CPLGetXMLValue(psLatLongBBox, "maxy", nullptr);
    1572           0 :                     if (pszMinX != nullptr && pszMinY != nullptr &&
    1573           0 :                         pszMaxX != nullptr && pszMaxY != nullptr)
    1574             :                     {
    1575             :                         // bFoundBBox = true;
    1576           0 :                         dfMinX = CPLAtof(pszMinX);
    1577           0 :                         dfMinY = CPLAtof(pszMinY);
    1578           0 :                         dfMaxX = CPLAtof(pszMaxX);
    1579           0 :                         dfMaxY = CPLAtof(pszMaxY);
    1580             :                     }
    1581             :                 }
    1582             : 
    1583         213 :                 char *pszCSVEscaped = CPLEscapeString(l_pszName, -1, CPLES_CSV);
    1584         213 :                 osLayerMetadataCSV += pszCSVEscaped;
    1585         213 :                 CPLFree(pszCSVEscaped);
    1586             : 
    1587         213 :                 osLayerMetadataCSV += ",";
    1588         213 :                 if (pszTitle)
    1589             :                 {
    1590          34 :                     pszCSVEscaped = CPLEscapeString(pszTitle, -1, CPLES_CSV);
    1591          34 :                     osLayerMetadataCSV += pszCSVEscaped;
    1592          34 :                     CPLFree(pszCSVEscaped);
    1593             :                 }
    1594         213 :                 osLayerMetadataCSV += ",";
    1595         213 :                 if (pszAbstract)
    1596             :                 {
    1597          28 :                     pszCSVEscaped = CPLEscapeString(pszAbstract, -1, CPLES_CSV);
    1598          28 :                     osLayerMetadataCSV += pszCSVEscaped;
    1599          28 :                     CPLFree(pszCSVEscaped);
    1600             :                 }
    1601         213 :                 osLayerMetadataCSV += "\n";
    1602             : 
    1603             :                 OGRWFSLayer *poLayer =
    1604             :                     new OGRWFSLayer(this, poSRS, bAxisOrderAlreadyInverted,
    1605         213 :                                     osBaseURL, l_pszName, pszNS, pszNSVal);
    1606         213 :                 if (!osOutputFormat.empty())
    1607           0 :                     poLayer->SetRequiredOutputFormat(osOutputFormat);
    1608             : 
    1609         213 :                 if (pszTitle)
    1610          34 :                     poLayer->SetMetadataItem("TITLE", pszTitle);
    1611         213 :                 if (pszAbstract)
    1612          28 :                     poLayer->SetMetadataItem("ABSTRACT", pszAbstract);
    1613         213 :                 CPLXMLNode *psKeywords = CPLGetXMLNode(psChildIter, "Keywords");
    1614         213 :                 if (psKeywords)
    1615             :                 {
    1616          20 :                     int nKeywordCounter = 1;
    1617          20 :                     for (CPLXMLNode *psKeyword = psKeywords->psChild;
    1618          75 :                          psKeyword != nullptr; psKeyword = psKeyword->psNext)
    1619             :                     {
    1620          55 :                         if (psKeyword->eType == CXT_Element &&
    1621          55 :                             psKeyword->psChild != nullptr)
    1622             :                         {
    1623          55 :                             poLayer->SetMetadataItem(
    1624             :                                 CPLSPrintf("KEYWORD_%d", nKeywordCounter),
    1625          55 :                                 psKeyword->psChild->pszValue);
    1626          55 :                             nKeywordCounter++;
    1627             :                         }
    1628           0 :                         else if (psKeyword->eType == CXT_Text)
    1629             :                         {
    1630           0 :                             poLayer->SetMetadataItem("KEYWORDS",
    1631           0 :                                                      psKeyword->pszValue);
    1632             :                         }
    1633             :                     }
    1634             :                 }
    1635             : 
    1636         213 :                 if (poSRS)
    1637             :                 {
    1638         194 :                     char *pszProj4 = nullptr;
    1639         194 :                     if (poSRS->exportToProj4(&pszProj4) == OGRERR_NONE)
    1640             :                     {
    1641             :                         /* See http://trac.osgeo.org/gdal/ticket/4041 */
    1642         388 :                         const bool bTrustBounds = CPLFetchBool(
    1643             :                             papszOpenOptionsIn, "TRUST_CAPABILITIES_BOUNDS",
    1644         194 :                             CPLTestBool(CPLGetConfigOption(
    1645             :                                 "OGR_WFS_TRUST_CAPABILITIES_BOUNDS", "FALSE")));
    1646             : 
    1647         194 :                         if (((bTrustBounds ||
    1648         177 :                               (dfMinX == -180 && dfMinY == -90 &&
    1649         137 :                                dfMaxX == 180 && dfMaxY == 90)) &&
    1650         154 :                              strcmp(pszProj4,
    1651             :                                     "+proj=longlat +datum=WGS84 +no_defs") ==
    1652          50 :                                  0) ||
    1653          50 :                             strcmp(pszDefaultSRS,
    1654             :                                    "urn:ogc:def:crs:OGC:1.3:CRS84") == 0)
    1655             :                         {
    1656         144 :                             poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
    1657             :                                                      dfMaxY);
    1658         144 :                             poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
    1659             :                         }
    1660             : 
    1661          50 :                         else if (bTrustBounds)
    1662             :                         {
    1663          16 :                             OGRSpatialReference oWGS84;
    1664           8 :                             oWGS84.SetWellKnownGeogCS("WGS84");
    1665           8 :                             oWGS84.SetAxisMappingStrategy(
    1666             :                                 OAMS_TRADITIONAL_GIS_ORDER);
    1667           8 :                             CPLPushErrorHandler(CPLQuietErrorHandler);
    1668             :                             auto poCT =
    1669             :                                 std::unique_ptr<OGRCoordinateTransformation>(
    1670             :                                     OGRCreateCoordinateTransformation(&oWGS84,
    1671          16 :                                                                       poSRS));
    1672           8 :                             if (poCT)
    1673             :                             {
    1674           8 :                                 poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX,
    1675             :                                                          dfMaxY);
    1676           8 :                                 poCT->TransformBounds(dfMinX, dfMinY, dfMaxX,
    1677             :                                                       dfMaxY, &dfMinX, &dfMinY,
    1678           8 :                                                       &dfMaxX, &dfMaxY, 20);
    1679           8 :                                 poLayer->SetExtents(dfMinX, dfMinY, dfMaxX,
    1680             :                                                     dfMaxY);
    1681             :                             }
    1682           8 :                             CPLPopErrorHandler();
    1683           8 :                             CPLErrorReset();
    1684             :                         }
    1685             :                     }
    1686         194 :                     CPLFree(pszProj4);
    1687             :                 }
    1688         213 :                 poLayer->SetSupportedSRSList(std::move(aosSupportedCRSList),
    1689         213 :                                              std::move(apoSupportedCRSList));
    1690             : 
    1691         426 :                 papoLayers = static_cast<OGRWFSLayer **>(CPLRealloc(
    1692         213 :                     papoLayers, sizeof(OGRWFSLayer *) * (nLayers + 1)));
    1693         213 :                 papoLayers[nLayers++] = poLayer;
    1694             : 
    1695         213 :                 if (psFileXML != nullptr)
    1696             :                 {
    1697          10 :                     CPLXMLNode *psIter = psXML->psChild;
    1698          36 :                     while (psIter)
    1699             :                     {
    1700          34 :                         if (psIter->eType == CXT_Element && psIter->psChild &&
    1701          82 :                             EQUAL(psIter->pszValue, "OGRWFSLayer") &&
    1702          14 :                             strcmp(CPLGetXMLValue(psIter, "name", ""),
    1703             :                                    l_pszName) == 0)
    1704             :                         {
    1705             :                             const CPLXMLNode *psSchema =
    1706           8 :                                 WFSFindNode(psIter->psChild, "schema");
    1707           8 :                             if (psSchema)
    1708             :                             {
    1709             :                                 OGRFeatureDefn *poSrcFDefn =
    1710           8 :                                     poLayer->ParseSchema(psSchema);
    1711           8 :                                 if (poSrcFDefn)
    1712           8 :                                     poLayer->BuildLayerDefn(poSrcFDefn);
    1713             :                             }
    1714           8 :                             break;
    1715             :                         }
    1716          26 :                         psIter = psIter->psNext;
    1717             :                     }
    1718             :                 }
    1719             :             }
    1720             :         }
    1721             :     }
    1722             : 
    1723         135 :     CSLDestroy(papszTypenames);
    1724             : 
    1725         135 :     if (!psFileXML)
    1726         129 :         CPLDestroyXMLNode(psXML);
    1727         135 :     CPLDestroyXMLNode(psStrippedXML);
    1728             : 
    1729         135 :     return TRUE;
    1730             : }
    1731             : 
    1732             : /************************************************************************/
    1733             : /*                       LoadMultipleLayerDefn()                        */
    1734             : /************************************************************************/
    1735             : 
    1736             : /* TinyOWS doesn't support POST, but MapServer, GeoServer and Deegree do */
    1737             : #define USE_GET_FOR_DESCRIBE_FEATURE_TYPE 1
    1738             : 
    1739          53 : void OGRWFSDataSource::LoadMultipleLayerDefn(const char *pszLayerName,
    1740             :                                              char *pszNS, char *pszNSVal)
    1741             : {
    1742          53 :     if (!bLoadMultipleLayerDefn)
    1743          23 :         return;
    1744             : 
    1745          44 :     if (aoSetAlreadyTriedLayers.find(pszLayerName) !=
    1746          88 :         aoSetAlreadyTriedLayers.end())
    1747           0 :         return;
    1748             : 
    1749          44 :     std::string osPrefix(pszLayerName);
    1750          44 :     const auto nColumnPos = osPrefix.find(':');
    1751          44 :     if (nColumnPos == std::string::npos)
    1752          38 :         osPrefix.clear();
    1753             :     else
    1754           6 :         osPrefix.resize(nColumnPos);
    1755             : 
    1756             :     OGRWFSLayer *poRefLayer =
    1757          44 :         dynamic_cast<OGRWFSLayer *>(GetLayerByName(pszLayerName));
    1758          44 :     if (poRefLayer == nullptr)
    1759           0 :         return;
    1760             : 
    1761          44 :     const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
    1762             : 
    1763             : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
    1764          44 :     CPLString osLayerToFetch(pszLayerName);
    1765             : #else
    1766             :     CPLString osTypeNameToPost;
    1767             :     osTypeNameToPost += "  <TypeName>";
    1768             :     osTypeNameToPost += pszLayerName;
    1769             :     osTypeNameToPost += "</TypeName>\n";
    1770             : #endif
    1771             : 
    1772          44 :     int nLayersToFetch = 1;
    1773          44 :     aoSetAlreadyTriedLayers.insert(pszLayerName);
    1774             : 
    1775         144 :     for (int i = 0; i < nLayers; i++)
    1776             :     {
    1777         100 :         if (!papoLayers[i]->HasLayerDefn())
    1778             :         {
    1779             :             /* We must be careful to requests only layers with the same
    1780             :              * prefix/namespace */
    1781         100 :             const char *l_pszName = papoLayers[i]->GetName();
    1782         188 :             if (((osPrefix.empty() && strchr(l_pszName, ':') == nullptr) ||
    1783          12 :                  (!osPrefix.empty() &&
    1784          12 :                   strncmp(l_pszName, osPrefix.c_str(), osPrefix.size()) == 0 &&
    1785         298 :                   l_pszName[osPrefix.size()] == ':')) &&
    1786          98 :                 ((pszRequiredOutputFormat == nullptr &&
    1787          98 :                   papoLayers[i]->GetRequiredOutputFormat() == nullptr) ||
    1788           0 :                  (pszRequiredOutputFormat != nullptr &&
    1789           0 :                   papoLayers[i]->GetRequiredOutputFormat() != nullptr &&
    1790           0 :                   strcmp(pszRequiredOutputFormat,
    1791           0 :                          papoLayers[i]->GetRequiredOutputFormat()) == 0)))
    1792             :             {
    1793          98 :                 if (aoSetAlreadyTriedLayers.find(l_pszName) !=
    1794         196 :                     aoSetAlreadyTriedLayers.end())
    1795          44 :                     continue;
    1796          54 :                 aoSetAlreadyTriedLayers.insert(l_pszName);
    1797             : 
    1798             : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
    1799          54 :                 if (nLayersToFetch > 0)
    1800          54 :                     osLayerToFetch += ",";
    1801          54 :                 osLayerToFetch += papoLayers[i]->GetName();
    1802             : #else
    1803             :                 osTypeNameToPost += "  <TypeName>";
    1804             :                 osTypeNameToPost += l_pszName;
    1805             :                 osTypeNameToPost += "</TypeName>\n";
    1806             : #endif
    1807          54 :                 nLayersToFetch++;
    1808             : 
    1809             :                 /* Avoid fetching to many layer definition at a time */
    1810          54 :                 if (nLayersToFetch >= 50)
    1811           0 :                     break;
    1812             :             }
    1813             :         }
    1814             :     }
    1815             : 
    1816             : #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1
    1817          44 :     CPLString osURL(osBaseURL);
    1818          44 :     osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS");
    1819          44 :     osURL = CPLURLAddKVP(osURL, "VERSION", GetVersion());
    1820          44 :     osURL = CPLURLAddKVP(osURL, "REQUEST", "DescribeFeatureType");
    1821          44 :     osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(osLayerToFetch));
    1822          44 :     osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr);
    1823          44 :     osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr);
    1824          44 :     osURL = CPLURLAddKVP(osURL, "FILTER", nullptr);
    1825          88 :     osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT",
    1826             :                          pszRequiredOutputFormat
    1827          44 :                              ? WFS_EscapeURL(pszRequiredOutputFormat).c_str()
    1828          44 :                              : nullptr);
    1829             : 
    1830          44 :     if (pszNS && GetNeedNAMESPACE())
    1831             :     {
    1832             :         /* Older Deegree version require NAMESPACE */
    1833             :         /* This has been now corrected */
    1834           0 :         CPLString osValue("xmlns(");
    1835           0 :         osValue += pszNS;
    1836           0 :         osValue += "=";
    1837           0 :         osValue += pszNSVal;
    1838           0 :         osValue += ")";
    1839           0 :         osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue));
    1840             :     }
    1841             : 
    1842          44 :     CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr);
    1843             : #else
    1844             :     CPLString osPost;
    1845             :     osPost += "<?xml version=\"1.0\"?>\n";
    1846             :     osPost +=
    1847             :         "<wfs:DescribeFeatureType xmlns:wfs=\"http://www.opengis.net/wfs\"\n";
    1848             :     osPost += "                 "
    1849             :               "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n";
    1850             :     osPost += "                 service=\"WFS\" version=\"";
    1851             :     osPost += GetVersion();
    1852             :     osPost += "\"\n";
    1853             :     osPost += "                 xmlns:gml=\"http://www.opengis.net/gml\"\n";
    1854             :     osPost += "                 xmlns:ogc=\"http://www.opengis.net/ogc\"\n";
    1855             :     if (pszNS && pszNSVal)
    1856             :     {
    1857             :         osPost += "                 xmlns:";
    1858             :         osPost += pszNS;
    1859             :         osPost += "=\"";
    1860             :         osPost += pszNSVal;
    1861             :         osPost += "\"\n";
    1862             :     }
    1863             :     osPost +=
    1864             :         "                 xsi:schemaLocation=\"http://www.opengis.net/wfs "
    1865             :         "http://schemas.opengis.net/wfs/";
    1866             :     osPost += GetVersion();
    1867             :     osPost += "/wfs.xsd\"";
    1868             :     const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat();
    1869             :     if (pszRequiredOutputFormat)
    1870             :     {
    1871             :         osPost += "\n";
    1872             :         osPost += "                 outputFormat=\"";
    1873             :         osPost += pszRequiredOutputFormat;
    1874             :         osPost += "\"";
    1875             :     }
    1876             :     osPost += ">\n";
    1877             :     osPost += osTypeNameToPost;
    1878             :     osPost += "</wfs:DescribeFeatureType>\n";
    1879             : 
    1880             :     // CPLDebug("WFS", "%s", osPost.c_str());
    1881             : 
    1882             :     CSLConstList papszOptions = NULL;
    1883             :     papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str());
    1884             :     papszOptions =
    1885             :         CSLAddNameValue(papszOptions, "HEADERS",
    1886             :                         "Content-Type: application/xml; charset=UTF-8");
    1887             : 
    1888             :     CPLHTTPResult *psResult = HTTPFetch(GetPostTransactionURL(), papszOptions);
    1889             :     CSLDestroy(papszOptions);
    1890             : #endif
    1891             : 
    1892          44 :     if (psResult == nullptr)
    1893             :     {
    1894           8 :         bLoadMultipleLayerDefn = false;
    1895           8 :         return;
    1896             :     }
    1897             : 
    1898          36 :     if (strstr(reinterpret_cast<const char *>(psResult->pabyData),
    1899             :                "<ServiceExceptionReport") != nullptr)
    1900             :     {
    1901           2 :         if (IsOldDeegree(reinterpret_cast<const char *>(psResult->pabyData)))
    1902             :         {
    1903             :             /* just silently forgive */
    1904             :         }
    1905             :         else
    1906             :         {
    1907           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    1908             :                      "Error returned by server : %s", psResult->pabyData);
    1909             :         }
    1910           2 :         CPLHTTPDestroyResult(psResult);
    1911           2 :         bLoadMultipleLayerDefn = false;
    1912           2 :         return;
    1913             :     }
    1914             : 
    1915             :     CPLXMLNode *psXML =
    1916          34 :         CPLParseXMLString(reinterpret_cast<const char *>(psResult->pabyData));
    1917          34 :     if (psXML == nullptr)
    1918             :     {
    1919           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s",
    1920             :                  psResult->pabyData);
    1921           2 :         CPLHTTPDestroyResult(psResult);
    1922           2 :         bLoadMultipleLayerDefn = false;
    1923           2 :         return;
    1924             :     }
    1925          32 :     CPLHTTPDestroyResult(psResult);
    1926             : 
    1927          32 :     const CPLXMLNode *psSchema = WFSFindNode(psXML, "schema");
    1928          32 :     if (psSchema == nullptr)
    1929             :     {
    1930           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <Schema>");
    1931           2 :         CPLDestroyXMLNode(psXML);
    1932           2 :         bLoadMultipleLayerDefn = false;
    1933           2 :         return;
    1934             :     }
    1935             : 
    1936          60 :     const CPLString osTmpFileName = VSIMemGenerateHiddenFilename("file.xsd");
    1937          30 :     CPLSerializeXMLTreeToFile(psSchema, osTmpFileName);
    1938             : 
    1939          60 :     std::vector<GMLFeatureClass *> aosClasses;
    1940          30 :     bool bFullyUnderstood = false;
    1941          30 :     bool bUseSchemaImports = false;
    1942          30 :     GMLParseXSD(osTmpFileName, bUseSchemaImports, aosClasses, bFullyUnderstood);
    1943             : 
    1944          30 :     int nLayersFound = 0;
    1945          30 :     if (!aosClasses.empty())
    1946             :     {
    1947             :         std::vector<GMLFeatureClass *>::const_iterator oIter =
    1948          30 :             aosClasses.begin();
    1949             :         std::vector<GMLFeatureClass *>::const_iterator oEndIter =
    1950          30 :             aosClasses.end();
    1951          86 :         while (oIter != oEndIter)
    1952             :         {
    1953          56 :             GMLFeatureClass *poClass = *oIter;
    1954          56 :             ++oIter;
    1955             : 
    1956          56 :             OGRWFSLayer *poLayer = nullptr;
    1957             : 
    1958          56 :             if (bKeepLayerNamePrefix && pszNS != nullptr &&
    1959           0 :                 strchr(poClass->GetName(), ':') == nullptr)
    1960             :             {
    1961           0 :                 CPLString osWithPrefix(pszNS);
    1962           0 :                 osWithPrefix += ":";
    1963           0 :                 osWithPrefix += poClass->GetName();
    1964           0 :                 poLayer =
    1965           0 :                     dynamic_cast<OGRWFSLayer *>(GetLayerByName(osWithPrefix));
    1966             :             }
    1967             :             else
    1968          56 :                 poLayer = dynamic_cast<OGRWFSLayer *>(
    1969          56 :                     GetLayerByName(poClass->GetName()));
    1970             : 
    1971          56 :             if (poLayer)
    1972             :             {
    1973          54 :                 if (!poLayer->HasLayerDefn())
    1974             :                 {
    1975          54 :                     nLayersFound++;
    1976             : 
    1977          54 :                     CPLXMLNode *psSchemaForLayer = CPLCloneXMLTree(psSchema);
    1978          54 :                     CPLStripXMLNamespace(psSchemaForLayer, nullptr, TRUE);
    1979          54 :                     CPLXMLNode *psIter = psSchemaForLayer->psChild;
    1980          54 :                     bool bHasAlreadyImportedGML = false;
    1981          54 :                     bool bFoundComplexType = false;
    1982          54 :                     bool bFoundElement = false;
    1983         590 :                     while (psIter != nullptr)
    1984             :                     {
    1985         536 :                         CPLXMLNode *psIterNext = psIter->psNext;
    1986         536 :                         if (psIter->eType == CXT_Element &&
    1987         266 :                             strcmp(psIter->pszValue, "complexType") == 0)
    1988             :                         {
    1989             :                             const char *l_pszName =
    1990         106 :                                 CPLGetXMLValue(psIter, "name", "");
    1991         212 :                             CPLString osExpectedName(poLayer->GetShortName());
    1992         106 :                             osExpectedName += "Type";
    1993         212 :                             CPLString osExpectedName2(poLayer->GetShortName());
    1994         106 :                             osExpectedName2 += "_Type";
    1995         106 :                             if (strcmp(l_pszName, osExpectedName) == 0 ||
    1996         158 :                                 strcmp(l_pszName, osExpectedName2) == 0 ||
    1997          52 :                                 strcmp(l_pszName, poLayer->GetShortName()) == 0)
    1998             :                             {
    1999          54 :                                 bFoundComplexType = true;
    2000             :                             }
    2001             :                             else
    2002             :                             {
    2003          52 :                                 CPLRemoveXMLChild(psSchemaForLayer, psIter);
    2004          52 :                                 CPLDestroyXMLNode(psIter);
    2005         106 :                             }
    2006             :                         }
    2007         430 :                         else if (psIter->eType == CXT_Element &&
    2008         160 :                                  strcmp(psIter->pszValue, "element") == 0)
    2009             :                         {
    2010             :                             const char *l_pszName =
    2011         106 :                                 CPLGetXMLValue(psIter, "name", "");
    2012         212 :                             CPLString osExpectedName(poLayer->GetShortName());
    2013         106 :                             osExpectedName += "Type";
    2014         212 :                             CPLString osExpectedName2(poLayer->GetShortName());
    2015         106 :                             osExpectedName2 += "_Type";
    2016             : 
    2017             :                             const char *pszType =
    2018         106 :                                 CPLGetXMLValue(psIter, "type", "");
    2019         212 :                             CPLString osExpectedType(poLayer->GetName());
    2020         106 :                             osExpectedType += "Type";
    2021         212 :                             CPLString osExpectedType2(poLayer->GetName());
    2022         106 :                             osExpectedType2 += "_Type";
    2023         106 :                             if (strcmp(pszType, osExpectedType) == 0 ||
    2024          98 :                                 strcmp(pszType, osExpectedType2) == 0 ||
    2025         302 :                                 strcmp(pszType, poLayer->GetName()) == 0 ||
    2026          98 :                                 (strchr(pszType, ':') &&
    2027          98 :                                  (strcmp(strchr(pszType, ':') + 1,
    2028          52 :                                          osExpectedType) == 0 ||
    2029          52 :                                   strcmp(strchr(pszType, ':') + 1,
    2030             :                                          osExpectedType2) == 0)))
    2031             :                             {
    2032          54 :                                 bFoundElement = true;
    2033             :                             }
    2034         104 :                             else if (*pszType == '\0' &&
    2035           0 :                                      CPLGetXMLNode(psIter, "complexType") !=
    2036          52 :                                          nullptr &&
    2037           0 :                                      (strcmp(l_pszName, osExpectedName) == 0 ||
    2038           0 :                                       strcmp(l_pszName, osExpectedName2) == 0 ||
    2039           0 :                                       strcmp(l_pszName,
    2040             :                                              poLayer->GetShortName()) == 0))
    2041             :                             {
    2042           0 :                                 bFoundElement = true;
    2043           0 :                                 bFoundComplexType = true;
    2044             :                             }
    2045             :                             else
    2046             :                             {
    2047          52 :                                 CPLRemoveXMLChild(psSchemaForLayer, psIter);
    2048          52 :                                 CPLDestroyXMLNode(psIter);
    2049         106 :                             }
    2050             :                         }
    2051         702 :                         else if (psIter->eType == CXT_Element &&
    2052         378 :                                  strcmp(psIter->pszValue, "import") == 0 &&
    2053          54 :                                  strcmp(CPLGetXMLValue(psIter, "namespace", ""),
    2054             :                                         "http://www.opengis.net/gml") == 0)
    2055             :                         {
    2056          54 :                             if (bHasAlreadyImportedGML)
    2057             :                             {
    2058           0 :                                 CPLRemoveXMLChild(psSchemaForLayer, psIter);
    2059           0 :                                 CPLDestroyXMLNode(psIter);
    2060             :                             }
    2061             :                             else
    2062             :                             {
    2063          54 :                                 bHasAlreadyImportedGML = true;
    2064             :                             }
    2065             :                         }
    2066         536 :                         psIter = psIterNext;
    2067             :                     }
    2068             : 
    2069          54 :                     if (bFoundComplexType && bFoundElement)
    2070             :                     {
    2071             :                         OGRFeatureDefn *poSrcFDefn =
    2072          54 :                             poLayer->ParseSchema(psSchemaForLayer);
    2073          54 :                         if (poSrcFDefn)
    2074             :                         {
    2075          54 :                             poLayer->BuildLayerDefn(poSrcFDefn);
    2076          54 :                             SaveLayerSchema(poLayer->GetName(),
    2077             :                                             psSchemaForLayer);
    2078             :                         }
    2079             :                     }
    2080             : 
    2081          54 :                     CPLDestroyXMLNode(psSchemaForLayer);
    2082             :                 }
    2083             :                 else
    2084             :                 {
    2085           0 :                     CPLDebug("WFS",
    2086             :                              "Found several time schema for layer %s in "
    2087             :                              "server response. Should not happen",
    2088             :                              poClass->GetName());
    2089             :                 }
    2090             :             }
    2091          56 :             delete poClass;
    2092             :         }
    2093             :     }
    2094             : 
    2095          30 :     if (nLayersFound != nLayersToFetch)
    2096             :     {
    2097           4 :         CPLDebug("WFS", "Turn off loading of multiple layer definitions at a "
    2098             :                         "single time");
    2099           4 :         bLoadMultipleLayerDefn = false;
    2100             :     }
    2101             : 
    2102          30 :     VSIUnlink(osTmpFileName);
    2103             : 
    2104          30 :     CPLDestroyXMLNode(psXML);
    2105             : }
    2106             : 
    2107             : /************************************************************************/
    2108             : /*                          SaveLayerSchema()                           */
    2109             : /************************************************************************/
    2110             : 
    2111         135 : void OGRWFSDataSource::SaveLayerSchema(const char *pszLayerName,
    2112             :                                        const CPLXMLNode *psSchema)
    2113             : {
    2114         135 :     if (psFileXML != nullptr)
    2115             :     {
    2116           1 :         bRewriteFile = true;
    2117             :         CPLXMLNode *psLayerNode =
    2118           1 :             CPLCreateXMLNode(nullptr, CXT_Element, "OGRWFSLayer");
    2119           1 :         CPLSetXMLValue(psLayerNode, "#name", pszLayerName);
    2120           1 :         CPLAddXMLChild(psLayerNode, CPLCloneXMLTree(psSchema));
    2121           1 :         CPLAddXMLChild(psFileXML, psLayerNode);
    2122             :     }
    2123         135 : }
    2124             : 
    2125             : /************************************************************************/
    2126             : /*                            IsOldDeegree()                            */
    2127             : /************************************************************************/
    2128             : 
    2129           5 : bool OGRWFSDataSource::IsOldDeegree(const char *pszErrorString)
    2130             : {
    2131           5 :     if (!bNeedNAMESPACE &&
    2132           5 :         strstr(pszErrorString, "Invalid \"TYPENAME\" parameter. "
    2133             :                                "No binding for prefix") != nullptr)
    2134             :     {
    2135           0 :         bNeedNAMESPACE = true;
    2136           0 :         return true;
    2137             :     }
    2138           5 :     return false;
    2139             : }
    2140             : 
    2141             : /************************************************************************/
    2142             : /*                           WFS_EscapeURL()                            */
    2143             : /************************************************************************/
    2144             : 
    2145         567 : CPLString WFS_EscapeURL(const char *pszURL)
    2146             : {
    2147         567 :     CPLString osEscapedURL;
    2148             : 
    2149             :     /* Difference with CPLEscapeString(, CPLES_URL) : we do not escape */
    2150             :     /* colon (:) or comma (,). Causes problems with servers such as
    2151             :      * http://www.mapinfo.com/miwfs? */
    2152             : 
    2153       37446 :     for (int i = 0; pszURL[i] != '\0'; i++)
    2154             :     {
    2155       36879 :         char ch = pszURL[i];
    2156       36879 :         if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
    2157       10004 :             (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == ':' ||
    2158             :             ch == ',')
    2159             :         {
    2160       30943 :             osEscapedURL += ch;
    2161             :         }
    2162             :         else
    2163             :         {
    2164             :             char szPercentEncoded[10];
    2165        5936 :             snprintf(szPercentEncoded, sizeof(szPercentEncoded), "%%%02X",
    2166        5936 :                      reinterpret_cast<const unsigned char *>(pszURL)[i]);
    2167        5936 :             osEscapedURL += szPercentEncoded;
    2168             :         }
    2169             :     }
    2170             : 
    2171         567 :     return osEscapedURL;
    2172             : }
    2173             : 
    2174             : /************************************************************************/
    2175             : /*                           WFS_DecodeURL()                            */
    2176             : /************************************************************************/
    2177             : 
    2178         143 : CPLString WFS_DecodeURL(const CPLString &osSrc)
    2179             : {
    2180         143 :     CPLString ret;
    2181         143 :     for (size_t i = 0; i < osSrc.length(); i++)
    2182             :     {
    2183           0 :         if (osSrc[i] == '%' && i + 2 < osSrc.length())
    2184             :         {
    2185           0 :             unsigned int ii = 0;
    2186           0 :             sscanf(osSrc.substr(i + 1, 2).c_str(), "%x", &ii);
    2187           0 :             char ch = static_cast<char>(ii);
    2188           0 :             ret += ch;
    2189           0 :             i = i + 2;
    2190             :         }
    2191             :         else
    2192             :         {
    2193           0 :             ret += osSrc[i];
    2194             :         }
    2195             :     }
    2196         143 :     return ret;
    2197             : }
    2198             : 
    2199             : /************************************************************************/
    2200             : /*                             HTTPFetch()                              */
    2201             : /************************************************************************/
    2202             : 
    2203         500 : CPLHTTPResult *OGRWFSDataSource::HTTPFetch(const char *pszURL,
    2204             :                                            CSLConstList papszOptions)
    2205             : {
    2206         500 :     char **papszNewOptions = CSLDuplicate(papszOptions);
    2207         500 :     if (bUseHttp10)
    2208             :         papszNewOptions =
    2209           0 :             CSLAddNameValue(papszNewOptions, "HTTP_VERSION", "1.0");
    2210         500 :     if (papszHttpOptions)
    2211           0 :         papszNewOptions = CSLMerge(papszNewOptions, papszHttpOptions);
    2212         500 :     CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszNewOptions);
    2213         500 :     CSLDestroy(papszNewOptions);
    2214             : 
    2215         500 :     if (psResult == nullptr)
    2216             :     {
    2217           0 :         return nullptr;
    2218             :     }
    2219         500 :     if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr)
    2220             :     {
    2221             :         // A few buggy servers return chunked data with erroneous
    2222             :         // remaining bytes value curl does not like this. Retry with
    2223             :         // HTTP 1.0 protocol instead that does not support chunked
    2224             :         // data.
    2225          78 :         if (psResult->pszErrBuf &&
    2226          78 :             strstr(psResult->pszErrBuf,
    2227           0 :                    "transfer closed with outstanding read data remaining") &&
    2228           0 :             !bUseHttp10)
    2229             :         {
    2230           0 :             CPLDebug("WFS", "Probably buggy remote server. Retrying with HTTP "
    2231             :                             "1.0 protocol");
    2232           0 :             bUseHttp10 = true;
    2233           0 :             CPLHTTPDestroyResult(psResult);
    2234           0 :             return HTTPFetch(pszURL, papszOptions);
    2235             :         }
    2236             : 
    2237          78 :         CPLError(CE_Failure, CPLE_AppDefined,
    2238             :                  "Error returned by server : %s (%d)",
    2239          78 :                  (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown",
    2240             :                  psResult->nStatus);
    2241          78 :         CPLHTTPDestroyResult(psResult);
    2242          78 :         return nullptr;
    2243             :     }
    2244         422 :     if (psResult->pabyData == nullptr)
    2245             :     {
    2246          11 :         CPLError(CE_Failure, CPLE_AppDefined,
    2247             :                  "Empty content returned by server");
    2248          11 :         CPLHTTPDestroyResult(psResult);
    2249          11 :         return nullptr;
    2250             :     }
    2251         411 :     return psResult;
    2252             : }
    2253             : 
    2254             : /************************************************************************/
    2255             : /*                             ExecuteSQL()                             */
    2256             : /************************************************************************/
    2257             : 
    2258          60 : OGRLayer *OGRWFSDataSource::ExecuteSQL(const char *pszSQLCommand,
    2259             :                                        OGRGeometry *poSpatialFilter,
    2260             :                                        const char *pszDialect)
    2261             : 
    2262             : {
    2263          60 :     while (*pszSQLCommand &&
    2264          60 :            isspace(static_cast<unsigned char>(*pszSQLCommand)))
    2265           0 :         ++pszSQLCommand;
    2266             : 
    2267          60 :     swq_select_parse_options oParseOptions;
    2268          60 :     oParseOptions.poCustomFuncRegistrar = WFSGetCustomFuncRegistrar();
    2269             : 
    2270             :     /* -------------------------------------------------------------------- */
    2271             :     /*      Use generic implementation for recognized dialects              */
    2272             :     /* -------------------------------------------------------------------- */
    2273          60 :     if (IsGenericSQLDialect(pszDialect))
    2274             :     {
    2275           0 :         OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
    2276           0 :             pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
    2277           0 :         oMap[poResLayer] = nullptr;
    2278           0 :         return poResLayer;
    2279             :     }
    2280             : 
    2281             :     /* -------------------------------------------------------------------- */
    2282             :     /*      Deal with "SELECT _LAST_INSERTED_FIDS_ FROM layername" statement */
    2283             :     /* -------------------------------------------------------------------- */
    2284          60 :     if (STARTS_WITH_CI(pszSQLCommand, "SELECT _LAST_INSERTED_FIDS_ FROM "))
    2285             :     {
    2286           6 :         const char *pszIter = pszSQLCommand + 33;
    2287          74 :         while (*pszIter && *pszIter != ' ')
    2288          68 :             pszIter++;
    2289             : 
    2290          12 :         CPLString osName = pszSQLCommand + 33;
    2291           6 :         osName.resize(pszIter - (pszSQLCommand + 33));
    2292             :         OGRWFSLayer *poLayer =
    2293           6 :             dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName));
    2294           6 :         if (poLayer == nullptr)
    2295             :         {
    2296           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
    2297             :                      osName.c_str());
    2298           2 :             return nullptr;
    2299             :         }
    2300             : 
    2301             :         auto poMEMDS = std::unique_ptr<MEMDataset>(
    2302           8 :             MEMDataset::Create("dummy_name", 0, 0, 0, GDT_Unknown, nullptr));
    2303             :         OGRLayer *poMEMLayer =
    2304           4 :             poMEMDS->CreateLayer("FID_LIST", nullptr, wkbNone, nullptr);
    2305           4 :         OGRFieldDefn oFDefn("gml_id", OFTString);
    2306           4 :         CPL_IGNORE_RET_VAL(poMEMLayer->CreateField(&oFDefn));
    2307             : 
    2308           6 :         for (const auto &osFID : poLayer->GetLastInsertedFIDList())
    2309             :         {
    2310           2 :             OGRFeature oFeature(poMEMLayer->GetLayerDefn());
    2311           2 :             oFeature.SetField(0, osFID);
    2312           2 :             CPL_IGNORE_RET_VAL(poMEMLayer->CreateFeature(&oFeature));
    2313             :         }
    2314             : 
    2315             :         OGRLayer *poResLayer =
    2316           4 :             new OGRWFSWrappedResultLayer(poMEMDS.release(), poMEMLayer);
    2317           4 :         oMap[poResLayer] = nullptr;
    2318           4 :         return poResLayer;
    2319             :     }
    2320             : 
    2321             :     /* -------------------------------------------------------------------- */
    2322             :     /*      Deal with "DELETE FROM layer_name WHERE expression" statement   */
    2323             :     /* -------------------------------------------------------------------- */
    2324          54 :     if (STARTS_WITH_CI(pszSQLCommand, "DELETE FROM "))
    2325             :     {
    2326          12 :         const char *pszIter = pszSQLCommand + 12;
    2327         112 :         while (*pszIter && *pszIter != ' ')
    2328         100 :             pszIter++;
    2329          12 :         if (*pszIter == 0)
    2330             :         {
    2331           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Invalid statement");
    2332           2 :             return nullptr;
    2333             :         }
    2334             : 
    2335          20 :         CPLString osName = pszSQLCommand + 12;
    2336          10 :         osName.resize(pszIter - (pszSQLCommand + 12));
    2337             :         OGRWFSLayer *poLayer =
    2338          10 :             dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName));
    2339          10 :         if (poLayer == nullptr)
    2340             :         {
    2341           2 :             CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s",
    2342             :                      osName.c_str());
    2343           2 :             return nullptr;
    2344             :         }
    2345             : 
    2346          16 :         while (*pszIter == ' ')
    2347           8 :             pszIter++;
    2348           8 :         if (!STARTS_WITH_CI(pszIter, "WHERE "))
    2349             :         {
    2350           2 :             CPLError(CE_Failure, CPLE_AppDefined, "WHERE clause missing");
    2351           2 :             return nullptr;
    2352             :         }
    2353           6 :         pszIter += 5;
    2354             : 
    2355           6 :         const char *pszQuery = pszIter;
    2356             : 
    2357             :         /* Check with the generic SQL engine that this is a valid WHERE clause
    2358             :          */
    2359          12 :         OGRFeatureQuery oQuery;
    2360           6 :         OGRErr eErr = oQuery.Compile(poLayer->GetLayerDefn(), pszQuery);
    2361           6 :         if (eErr != OGRERR_NONE)
    2362             :         {
    2363           2 :             return nullptr;
    2364             :         }
    2365             : 
    2366             :         /* Now turn this into OGC Filter language if possible */
    2367           4 :         int bNeedsNullCheck = FALSE;
    2368           4 :         int nVersion = (strcmp(GetVersion(), "1.0.0") == 0) ? 100 : 110;
    2369             :         swq_expr_node *poNode =
    2370           4 :             static_cast<swq_expr_node *>(oQuery.GetSWQExpr());
    2371           4 :         poNode->ReplaceBetweenByGEAndLERecurse();
    2372           4 :         poNode->ReplaceInByOrRecurse();
    2373             :         CPLString osOGCFilter = WFS_TurnSQLFilterToOGCFilter(
    2374             :             poNode, nullptr, poLayer->GetLayerDefn(), nVersion,
    2375           4 :             bPropertyIsNotEqualToSupported, bUseFeatureId,
    2376           8 :             bGmlObjectIdNeedsGMLPrefix, "", &bNeedsNullCheck);
    2377           4 :         if (bNeedsNullCheck && !HasNullCheck())
    2378           0 :             osOGCFilter = "";
    2379             : 
    2380           4 :         if (osOGCFilter.empty())
    2381             :         {
    2382           2 :             CPLError(CE_Failure, CPLE_AppDefined,
    2383             :                      "Cannot convert WHERE clause into a OGC filter");
    2384           2 :             return nullptr;
    2385             :         }
    2386             : 
    2387           2 :         poLayer->DeleteFromFilter(osOGCFilter);
    2388             : 
    2389           2 :         return nullptr;
    2390             :     }
    2391             : 
    2392             :     /* -------------------------------------------------------------------- */
    2393             :     /*      Deal with "SELECT xxxx ORDER BY" statement                      */
    2394             :     /* -------------------------------------------------------------------- */
    2395          42 :     if (STARTS_WITH_CI(pszSQLCommand, "SELECT"))
    2396             :     {
    2397          42 :         swq_select *psSelectInfo = new swq_select();
    2398          42 :         if (psSelectInfo->preparse(pszSQLCommand, TRUE) != CE_None)
    2399             :         {
    2400           0 :             delete psSelectInfo;
    2401           0 :             return nullptr;
    2402             :         }
    2403          42 :         int iLayer = 0;
    2404          42 :         if (strcmp(GetVersion(), "1.0.0") != 0 &&
    2405          42 :             psSelectInfo->table_count == 1 &&
    2406          16 :             psSelectInfo->table_defs[0].data_source == nullptr &&
    2407           8 :             (iLayer = GetLayerIndex(psSelectInfo->table_defs[0].table_name)) >=
    2408           8 :                 0 &&
    2409          84 :             psSelectInfo->join_count == 0 && psSelectInfo->order_specs > 0 &&
    2410           0 :             psSelectInfo->poOtherSelect == nullptr)
    2411             :         {
    2412           0 :             OGRWFSLayer *poSrcLayer = papoLayers[iLayer];
    2413           0 :             std::vector<OGRWFSSortDesc> aoSortColumns;
    2414           0 :             int i = 0;  // Used after for.
    2415           0 :             for (; i < psSelectInfo->order_specs; i++)
    2416             :             {
    2417           0 :                 int nFieldIndex = poSrcLayer->GetLayerDefn()->GetFieldIndex(
    2418           0 :                     psSelectInfo->order_defs[i].field_name);
    2419           0 :                 if (poSrcLayer->HasGotApproximateLayerDefn() || nFieldIndex < 0)
    2420           0 :                     break;
    2421             : 
    2422             :                 /* Make sure to have the right case */
    2423           0 :                 const char *pszFieldName = poSrcLayer->GetLayerDefn()
    2424           0 :                                                ->GetFieldDefn(nFieldIndex)
    2425           0 :                                                ->GetNameRef();
    2426             : 
    2427             :                 aoSortColumns.emplace_back(
    2428           0 :                     pszFieldName, psSelectInfo->order_defs[i].ascending_flag);
    2429             :             }
    2430             : 
    2431           0 :             if (i == psSelectInfo->order_specs)
    2432             :             {
    2433           0 :                 OGRWFSLayer *poDupLayer = poSrcLayer->Clone();
    2434             : 
    2435           0 :                 poDupLayer->SetOrderBy(aoSortColumns);
    2436           0 :                 int nBackup = psSelectInfo->order_specs;
    2437           0 :                 psSelectInfo->order_specs = 0;
    2438           0 :                 char *pszSQLWithoutOrderBy = psSelectInfo->Unparse();
    2439           0 :                 CPLDebug("WFS", "SQL without ORDER BY: %s",
    2440             :                          pszSQLWithoutOrderBy);
    2441           0 :                 psSelectInfo->order_specs = nBackup;
    2442           0 :                 delete psSelectInfo;
    2443           0 :                 psSelectInfo = nullptr;
    2444             : 
    2445             :                 /* Just set poDupLayer in the papoLayers for the time of the */
    2446             :                 /* base ExecuteSQL(), so that the OGRGenSQLResultsLayer
    2447             :                  * references */
    2448             :                 /* that temporary layer */
    2449           0 :                 papoLayers[iLayer] = poDupLayer;
    2450             : 
    2451           0 :                 OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
    2452             :                     pszSQLWithoutOrderBy, poSpatialFilter, pszDialect,
    2453           0 :                     &oParseOptions);
    2454           0 :                 papoLayers[iLayer] = poSrcLayer;
    2455             : 
    2456           0 :                 CPLFree(pszSQLWithoutOrderBy);
    2457             : 
    2458           0 :                 if (poResLayer != nullptr)
    2459           0 :                     oMap[poResLayer] = poDupLayer;
    2460             :                 else
    2461           0 :                     delete poDupLayer;
    2462           0 :                 return poResLayer;
    2463             :             }
    2464             :         }
    2465          42 :         else if (bStandardJoinsWFS2 && psSelectInfo->join_count > 0 &&
    2466          34 :                  psSelectInfo->poOtherSelect == nullptr)
    2467             :         {
    2468             :             // Just to make sure everything is valid, but we won't use
    2469             :             // that one as we want to run the join on server-side
    2470          34 :             oParseOptions.bAllowFieldsInSecondaryTablesInWhere = TRUE;
    2471          34 :             oParseOptions.bAddSecondaryTablesGeometryFields = TRUE;
    2472          34 :             oParseOptions.bAlwaysPrefixWithTableName = TRUE;
    2473          34 :             oParseOptions.bAllowDistinctOnGeometryField = TRUE;
    2474          34 :             oParseOptions.bAllowDistinctOnMultipleFields = TRUE;
    2475             :             GDALSQLParseInfo *psParseInfo =
    2476          34 :                 BuildParseInfo(psSelectInfo, &oParseOptions);
    2477          34 :             oParseOptions.bAllowFieldsInSecondaryTablesInWhere = FALSE;
    2478          34 :             oParseOptions.bAddSecondaryTablesGeometryFields = FALSE;
    2479          34 :             oParseOptions.bAlwaysPrefixWithTableName = FALSE;
    2480          34 :             oParseOptions.bAllowDistinctOnGeometryField = FALSE;
    2481          34 :             oParseOptions.bAllowDistinctOnMultipleFields = FALSE;
    2482          34 :             const bool bOK = psParseInfo != nullptr;
    2483          34 :             DestroyParseInfo(psParseInfo);
    2484             : 
    2485          34 :             OGRLayer *poResLayer = nullptr;
    2486          34 :             if (bOK)
    2487             :             {
    2488          34 :                 poResLayer = OGRWFSJoinLayer::Build(this, psSelectInfo);
    2489          34 :                 oMap[poResLayer] = nullptr;
    2490             :             }
    2491             : 
    2492          34 :             delete psSelectInfo;
    2493          34 :             return poResLayer;
    2494             :         }
    2495             : 
    2496           8 :         delete psSelectInfo;
    2497             :     }
    2498             : 
    2499           8 :     OGRLayer *poResLayer = GDALDataset::ExecuteSQL(
    2500           8 :         pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions);
    2501           8 :     oMap[poResLayer] = nullptr;
    2502           8 :     return poResLayer;
    2503             : }
    2504             : 
    2505             : /************************************************************************/
    2506             : /*                          ReleaseResultSet()                          */
    2507             : /************************************************************************/
    2508             : 
    2509          40 : void OGRWFSDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
    2510             : {
    2511          40 :     if (poResultsSet == nullptr)
    2512           0 :         return;
    2513             : 
    2514          40 :     std::map<OGRLayer *, OGRLayer *>::iterator oIter = oMap.find(poResultsSet);
    2515          40 :     if (oIter != oMap.end())
    2516             :     {
    2517             :         /* Destroy first the result layer, because it still references */
    2518             :         /* the poDupLayer (oIter->second) */
    2519          40 :         delete poResultsSet;
    2520             : 
    2521          40 :         delete oIter->second;
    2522          40 :         oMap.erase(oIter);
    2523             :     }
    2524             :     else
    2525             :     {
    2526           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2527             :                  "Trying to destroy an invalid result set !");
    2528             :     }
    2529             : }

Generated by: LCOV version 1.14