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

Generated by: LCOV version 1.14